Toolbox-2/source/boulder/bellatrix/Boulderdash_Tile_TV.spin

606 lines
31 KiB
Plaintext

'' 2-bit paletted tile-based video driver
'' Based on the NTSC Spectrum-like TV Video Driver
'' ──────────────────────────────────────────────────────────────────────────────────
'' Version story:
''
'' 2007-11-25 2.0 Tiled Version (Spork Frog)
'' 2007-12-05 1.0 First version (José Luis Cebrián)
'' 2009-11-12 Halved tile horizontal resolution (Héctor Peraza)
'' to eliminate redundant pixel pairs
'' (BoulderDash uses 8x16 tiles).
'' This also halves tile memory requirements.
'' Fixed vertical sync pulse generation.
'' 2009-11-13 Changed pixel clock settings (Héctor Peraza)
'' in order to display 20 tiles per line,
'' as in the classic C64 BoulderDash.
'' 2009-11-14 Reverse pixel order before waitvid (Héctor Peraza)
'' to avoid having to define mirrored tiles.
'' 2009-11-15 Added an optional status line. (Héctor Peraza)
'' 2009-11-17 Video config parameters can be passed (Héctor Peraza)
'' on driver startup, to allow for different
'' configurations and/or platforms.
'' 2009-11-18 Implemented PAL mode. Color seems to (Héctor Peraza)
'' be a bit off in PAL mode.
'' 2009-12-05 Added the possibilty to halve tile (Héctor Peraza)
'' height (we need that for the title screen,
'' since it has text on the bottom part
'' and characters are half-size)
''
'' ──────────────────────────────────────────────────────────────────────────────────
'' This code is in the public domain. Feel free to use it in any way you like.
''
'' The screen for this driver is composed of two major parts:
''
'' 1. Tile map
'' Each of these entries is 1 byte long, representing the tile number.
'' There are 880 words total for a 40 by 22 tile screen. The displayed
'' area is 20 x 12 in NTSC mode, 20 x 14 in PAL mode.
''
'' 2. Tiles
'' Each tile is made up of 16 words with 2 bit color encoding on
'' each tile. You can define as few or as many tiles as you want,
'' but be sure not to use any tiles that you don't define.
''
'' There is also an optional status line at the top of the screen that does not
'' scroll with the rest. It is also composed of tiles, but these are only 8 pixels
'' high. Two empty raster lines separate the status from the main screen. The
'' foreground color can be set per character basis.
''
'' 80Mhz is *required* in order to output pixels fast enough.
CON
' Border offset, to center the image
HorizBorderOffset = 9 ' positive values move the screen to the right
VertBorderOffset = 10 ' positive values move the screen down
' Counter Module Configuration
' • CTRMode - Operating mode (0001 for Video Mode)
' • PLLDiv - Divisor for the VCO frequency (111: use VCO value as-is)
'┌─────────── CTRMode
'│ ┌───── PLLDiv
CTRA_TVGEN = %00001_111
' NTSC Color frequency in Hz
'
' This is the 'base' clock rate for all our NTSC timings. At start, the
' driver will program the FRQA register to output at this rate. Our base
' clock value is 1/16 of the NTSC clock rate, or approximately 0.01746 µs.
NTSC_ClockFreq = 3_579_545
' NTSC Timings table
' Time Clocks Output
' Total horizontal timing: 63.5 µs 3638
' Horizontal blanking period: 10.9 µs 624
' Front porch: 1.5 µs 86 * Black ($02)
' Synchronizing pulse: 4.7 µs 269 * Blank ($00)
' Back porch: 4.7 µs 269
' Breeze away: 0.6 µs 34 * Black ($02)
' Colour burst: 2.5 µs 144 * Y Hue ($8A)
' Wait to data: 1.6 µs 92 * Black ($02)
' Visible line 52.6 µs 3008
' Left border 2.5 µs 146 * Black ($00)
' Pixel data 47.5 µs 2720
' Character (x20) 2.4 µs 136 * Data
' Right border 2.6 µs 146 * Black ($00)
' Half visible line ¹ 20.8 µs 1195
'
' Lines marked with * are the actual parts of a visible line as sent to the TV.
'
' ¹ The vertical sync pulse is a series of half lines with inverted horizontal sync
' pulses (the so-called equalization pulses). These half lines should have a length
' of 3638/2 = 1819 clocks (that is, a 624-clocks HSync followed by about 1195 clocks
' of visible data).
VSCL_NTSC_FrontPorch = 85
VSCL_NTSC_SynchronizingPulse = 270
VSCL_NTSC_BackPorch = 34 + 144 + 92
VSCL_NTSC_BreezeAway = 34
VSCL_NTSC_ColourBurst = 144
VSCL_NTSC_WaitToData = 92
VSCL_NTSC_VisibleLine = 3008
VSCL_NTSC_HalfLine = 1192 ' NTSC Half line
VSCL_NTSC_LeftBorder = 146 + HorizBorderOffset
VSCL_NTSC_RightBorder = 146 - HorizBorderOffset
VSCL_NTSC_Character = (17 << 12) + 136 ' Eight double-width pixels
NTSC_VerticalLines = 262 - 9
NTSC_TopLine = (NTSC_VerticalLines + 192) / 2 - VertBorderOffset
NTSC_BottomLine = (NTSC_VerticalLines - 192) / 2 - VertBorderOffset
' PAL Color frequency in Hz
'
' This is the 'base' clock rate for all the PAL timings. At start, the
' driver will program the FRQA register to output at this rate. Our base
' clock value is 1/16 of the PAL clock rate, or approximately 0.014097 µs
PAL_ClockFreq = 4_433_618
' PAL Timings table
' Time Clocks Output
' Total horizontal timing: 64.0 µs 4540
' Horizontal blanking period: 12.0 µs 851
' Front porch: 1.6 µs 113 * Black ($02)
' Synchronizing pulse: 4.7 µs 333 * Blank ($00)
' Back porch: 5.7 µs 404
' Breeze away: 0.9 µs 64 * Black ($02)
' Colour burst: 2.3 µs 160 * Y Hue ($8A)
' Wait to data: 2.5 µs 180 * Black ($02)
' Visible line 52.0 µs 3689
' Left border 3.4 µs 244 * Black ($00)
' Pixel data 45.1 µs 3200
' Character (x20) 2.3 µs 160 * Data
' Right border 3.4 µs 245 * Black ($00)
' Half visible line ¹ 20.0 µs 1419
'
' Lines marked with * are the actual parts of a visible line as sent to the TV.
'
' ¹ The vertical sync pulse is a series of half lines with inverted horizontal sync
' pulses (the so-called equalization pulses). These half lines should have a length
' of 4540/2 = 2270 clocks (that is, a 851-clocks HSync followed by about 1419 clocks
' of visible data).
VSCL_PAL_FrontPorch = 114
VSCL_PAL_SynchronizingPulse = 332
VSCL_PAL_BackPorch = 64 + 160 + 180
VSCL_PAL_BreezeAway = 64
VSCL_PAL_ColourBurst = 160
VSCL_PAL_WaitToData = 180
VSCL_PAL_VisibleLine = 3689
VSCL_PAL_HalfLine = 1419 ' PAL Half line
VSCL_PAL_LeftBorder = 244 + HorizBorderOffset
VSCL_PAL_RightBorder = 245 - HorizBorderOffset
VSCL_PAL_Character = (20 << 12) + 160 ' Eight double-width pixels
PAL_VerticalLines = 312 - 9
PAL_TopLine = (PAL_VerticalLines + 224) / 2 - VertBorderOffset
PAL_BottomLine = (PAL_VerticalLines - 224) / 2 - VertBorderOffset
PUB Start(vParam)
'' Starts the TV driver and begins the output of NTSC video.
'' Uses a Cog.
''
'' Parameters:
'' vParam → Array with video parameters:
'' [0] → VCFG mode
'' [1] → Pingroup for VCFG pins
'' [2] → Pinmask for VCFG pins
'' [3] → Pinmask for output pins
'' [4] → Color standard: 0 = NTSC, 1 = PAL
'' [5] → Tile map address
'' [6] → Address of a vsync flag byte in HUB memory
cognew(@Entry, vParam)
DAT
org $000
Entry jmp #StartDriver
' ─────────────────────────────────────────
' Data section
' ─────────────────────────────────────────
' Colors used in waitvid
COLOR_SYNC long $00 ' Sync level is below black
COLOR_BLACK long $02
COLOR_YHUE_NTSC long $8A ' NTSC color burst
COLOR_YHUE_PAL_1 long $7A ' PAL swinging color burst
COLOR_YHUE_PAL_2 long $AA ' " " " "
COLOR_BORDER long $02 ' Black border
' The following constants are too big to use in-place, so we need to
' reserve some registers to put them here
_ScreenSize long 40 * 28 ' Tile map size
_NTSC_ClockFreq long NTSC_ClockFreq
_VSCL_NTSC_Character long VSCL_NTSC_Character
_VSCL_NTSC_VisibleLine long VSCL_NTSC_VisibleLine
_VSCL_NTSC_HalfLine long VSCL_NTSC_HalfLine
_PAL_ClockFreq long PAL_ClockFreq
_VSCL_PAL_Character long VSCL_PAL_Character
_VSCL_PAL_VisibleLine long VSCL_PAL_VisibleLine
_VSCL_PAL_HalfLine long VSCL_PAL_HalfLine
' Other constants
TrueWord long $FFFF
' ─────────────────────────────────────────
' Code section
' ─────────────────────────────────────────
StartDriver
' Configure the Cog generators
mov R0, PAR
rdlong _VCFG_Mode, R0
add R0, #4
rdlong _VCFG_PinGroup, R0
add R0, #4
rdlong _VCFG_PinMask, R0
add R0, #4
rdlong _PortMask, R0
add R0, #4
rdlong VideoMode, R0
add R0, #4
rdlong ScreenPtr, R0
add R0, #4
rdlong VSyncPtr, R0
test VideoMode, #1 wz
if_z mov _ClockFreq, _NTSC_ClockFreq
if_nz mov _ClockFreq, _PAL_ClockFreq
if_z mov _VSCL_Character, _VSCL_NTSC_Character
if_nz mov _VSCL_Character, _VSCL_PAL_Character
if_z mov _VSCL_VisibleLine, _VSCL_NTSC_VisibleLine
if_nz mov _VSCL_VisibleLine, _VSCL_PAL_VisibleLine
if_z mov _VSCL_HalfLine, _VSCL_NTSC_HalfLine
if_nz mov _VSCL_HalfLine, _VSCL_PAL_HalfLine
if_z mov _VSCL_LeftBorder, #VSCL_NTSC_LeftBorder
if_nz mov _VSCL_LeftBorder, #VSCL_PAL_LeftBorder
if_z mov _VSCL_RightBorder, #VSCL_NTSC_RightBorder
if_nz mov _VSCL_RightBorder, #VSCL_PAL_RightBorder
if_z mov _VSCL_FrontPorch, #VSCL_NTSC_FrontPorch
if_nz mov _VSCL_FrontPorch, #VSCL_PAL_FrontPorch
if_z mov _VSCL_SynchronizingPulse, #VSCL_NTSC_SynchronizingPulse
if_nz mov _VSCL_SynchronizingPulse, #VSCL_PAL_SynchronizingPulse
if_z mov _VSCL_BreezeAway, #VSCL_NTSC_BreezeAway
if_nz mov _VSCL_BreezeAway, #VSCL_PAL_BreezeAway
if_z mov _VSCL_ColourBurst, #VSCL_NTSC_ColourBurst
if_nz mov _VSCL_ColourBurst, #VSCL_PAL_ColourBurst
if_z mov _VSCL_BackPorch, #VSCL_NTSC_BackPorch
if_nz mov _VSCL_BackPorch, #VSCL_PAL_BackPorch
if_z mov _VSCL_WaitToData, #VSCL_NTSC_WaitToData
if_nz mov _VSCL_WaitToData, #VSCL_PAL_WaitToData
if_z mov _VerticalLines, #NTSC_VerticalLines
if_nz mov _VerticalLines, #PAL_VerticalLines
if_z mov _TopLine, #NTSC_TopLine
if_nz mov _TopLine, #PAL_TopLine
if_z mov _BottomLine, #NTSC_BottomLine
if_nz mov _BottomLine, #PAL_BottomLine
if_z mov _Burst1, COLOR_YHUE_NTSC
if_nz mov _Burst1, COLOR_YHUE_PAL_1
if_z mov _Burst2, COLOR_YHUE_NTSC
if_nz mov _Burst2, COLOR_YHUE_PAL_2
movs VCFG, _VCFG_PinMask ' VCFG'S = pinmask (pin31: 0000_0111 : pin24)
movd VCFG, _VCFG_PinGroup ' VCFG'D = pingroup (grp. 3 i.e. pins 24-31)
movi VCFG, _VCFG_Mode ' Baseband video on bottom nibble, 2-bit color, enable chroma on baseband
or DIRA, _PortMask ' Setup the port mask for DAC access (set DAC pins to output)
movi CTRA, #CTRA_TVGEN ' Setup the Counter Module Generator A
mov R1, _ClockFreq ' R1 := Video Clock Frequency in Hz
rdlong R2, #0 ' R2 := Current CPU Clock Frequency in Hz
call #Divide ' R3 := R1÷R2 (fractional part)
mov FRQA, R3 ' Setup the Counter Module Generator frequency
mov phaseflip, #0
' Frame loop
:Frame
mov LineCounter, _VerticalLines ' LineCounter := Number of vertical lines
mov CharacterRows, #0
' Copy the screen parameters to local variables in Cog memory
mov AttribPtr, ScreenPtr
mov TilePtr, AttribPtr
add TilePtr, _ScreenSize
rdlong COLORS, TilePtr
add TilePtr, #4
rdlong OffsetX, TilePtr
mov OffsetY, OffsetX
shr OffsetX, #16
and OffsetY, TrueWord
add TilePtr, #4
rdlong Status, TilePtr
add TilePtr, #4
mov StatusPtr, TilePtr
add TilePtr, #20*2
' Y Offset Calculations
mov R0, OffsetY
test Status, #2 wz
if_z and OffsetY, #%1111
if_nz and OffsetY, #%111
shl OffsetY, #1
mov CharacterRows, OffsetY
if_z shr R0, #4
if_nz shr R0, #3
add R0, #1
:AddLoop add AttribPtr, #40
djnz R0, #:AddLoop
sub AttribPtr, #40
' X Offset Calculations
mov R0, OffsetX
and OffsetX, #%111
shl OffsetX, #1
mov R5, #16
sub R5, OffsetX
shr R0, #3
add AttribPtr, R0
' Status Line row count
mov R6, #0
' Visible line loop
:ScanLine call #HSync
' Check if the current line is in the top or bottom border
cmp LineCounter, _BottomLine wc
if_c jmp #:EmptyLine
cmp LineCounter, _TopLine wc
if_nc jmp #:CheckStat
jmp #:DataLine
:CheckStat test Status, #1 wz
if_z jmp #:EmptyLine
mov R0, _TopLine
add R0, #10
cmp LineCounter, R0 wc
if_nc jmp #:EmptyLine
cmp R6, #16 wz
if_z jmp #:EmptyLine
' Draw the status line
mov VSCL, _VSCL_LeftBorder
waitvid COLOR_BORDER, #0
' Character rendering loop
mov VSCL, _VSCL_Character
mov R0, #20 ' R0 := Character counter (per line)
mov R1, StatusPtr ' R1 := Pointer to status line
mov R4, R1
add R4, R0 ' R4 := Pointer to foreground color
:StatusChar rdbyte R3, R1 ' R3 := Tile description
add R1, #1 ' Advance the attribute pointer for the next character
shl R3, #4 ' Multiply by bitmap size to form an address
add R3, R6 ' Add the row offset for each tile
add R3, TilePtr ' R3 := Address of the bitmap word to read
rdword PIXELS, R3 ' Set pixels to the proper data
rev PIXELS, #16 ' Reverse bit order to output pixels MSB first
rdbyte R3, R4 ' R3 := foreground color
add R4, #1 ' Advance the color pointer for the next character
mov R2, COLORS
shl R2, #8
or R2, R3 ' Only foreground color is set, background remains the same from palette
ror R2, #8
test VideoMode, #1 wz
if_nz xor R2, phaseflip ' Flip color phase if PAL mode
waitvid R2, PIXELS ' Output our video
djnz R0, #:StatusChar ' and loop
mov VSCL, _VSCL_RightBorder
waitvid COLOR_BORDER, #0 ' Output the right border
add R6, #2
djnz LineCounter, #:ScanLine ' Next line (note that here LineCounter is always > 0)
' jmp #:EmptyLine
' Draw a data line
:DataLine mov VSCL, _VSCL_LeftBorder
waitvid COLOR_BORDER, #0 ' Output the left border
' Character rendering loop
mov VSCL, _VSCL_Character
mov R0, #20 ' R0 := Character counter (per line)
mov R1, AttribPtr ' R1 := Pointer to attribute (tile) area
:Character rdbyte R2, R1 ' R2 := Tile description long
add R1, #1 ' Advance the attribute pointer for the next character
test Status, #2 wz
if_z shl R2, #5 ' Multiply by bitmap size to form an address
if_nz shl R2, #4
add R2, CharacterRows ' Add the row offset for each tile
add R2, TilePtr ' R2 := Address of the bitmap word to read
rdword PIXELS, R2 ' Set pixels to the proper data
cmp OffsetX, #0 wz
if_z jmp #:Output
shl PIXELS, OffsetX
rdbyte R2, R1
test Status, #2 wz
if_z shl R2, #5 ' Multiply by bitmap size to form an address
if_nz shl R2, #4
add R2, CharacterRows ' Add the row offset for each tile
add R2, TilePtr ' R2 := Address of the bitmap word to read
rdword R3, R2
shr R3, R5
or PIXELS, R3
:Output rev PIXELS, #16 ' Reverse bit order to output pixels MSB first
mov R2, COLORS
test VideoMode, #1 wz
if_nz xor R2, phaseflip ' Flip color phase if PAL mode
waitvid R2, PIXELS ' Output our video
djnz R0, #:Character ' and loop
mov VSCL, _VSCL_RightBorder
waitvid COLOR_BORDER, #0 ' Output the right border
' Calculate the address of the next attribute line
add CharacterRows, #2
test Status, #2 wz
if_z mov R2, #32
if_nz mov R2, #16
cmp CharacterRows, R2 wz
if_z mov CharacterRows, #0 ' Advance to the next line of attributes if the sub-character counter
if_z add AttribPtr, #40 ' reaches the end of character, and reset it to 8
djnz LineCounter, #:ScanLine ' Next line (note that here LineCounter is always > 0)
' Empty (border only) lines
:EmptyLine mov VSCL, _VSCL_VisibleLine ' Output an entire visible line of BORDER color
waitvid COLOR_BORDER, #0
djnz LineCounter, #:ScanLine ' Next line: note that this may be the last one
' VSync
mov R0, #$FF
wrbyte R0, VSyncPtr
test VideoMode, #1 wz
if_nz xor phaseflip, phasemask
call #VSyncHigh ' VSync procedure: 6 half-lines of HSync-only values (3 lines)
call #VSyncLow ' 6 half-lines inverted from the previous ones (3 lines)
call #VSyncHigh ' 6 half-lines more of HSync-only values (3 lines)
mov R0, #0
wrbyte R0, VSyncPtr
jmp #:Frame ' Next frame
' ─────────────────────────────────────────
' Synchronization subroutines
' ─────────────────────────────────────────
HSync mov VSCL, _VSCL_FrontPorch
waitvid COLOR_BLACK, #0
mov VSCL, _VSCL_SynchronizingPulse
waitvid COLOR_SYNC, #0
mov VSCL, _VSCL_BreezeAway
waitvid COLOR_BLACK, #0
mov VSCL, _VSCL_ColourBurst
test VideoMode, #1 wz
if_nz xor phaseflip, phasemask wz ' Swing color burst if PAL
if_z mov R0, _Burst1
if_nz mov R0, _Burst2
waitvid R0, #0
mov VSCL, _VSCL_WaitToData
waitvid COLOR_BLACK, #0
HSync_Ret ret
VSyncHigh mov R0, #6
:Loop mov VSCL, _VSCL_FrontPorch
waitvid COLOR_BLACK, #0
mov R1, _VSCL_SynchronizingPulse
shr R1, #1 ' Half-length pulses
mov VSCL, R1
waitvid COLOR_SYNC, #0
mov VSCL, R1
waitvid COLOR_BLACK, #0
mov VSCL, _VSCL_BackPorch ' BackPorch = BreezeAway + ColourBurst + WaitToData
waitvid COLOR_BLACK, #0
mov VSCL, _VSCL_HalfLine
waitvid COLOR_BLACK, #0
djnz R0, #:Loop
VSyncHigh_Ret ret
VSyncLow mov R0, #6
:Loop mov VSCL, _VSCL_FrontPorch
waitvid COLOR_BLACK, #0
mov VSCL, _VSCL_HalfLine
waitvid COLOR_SYNC, #0
mov VSCL, _VSCL_BackPorch ' BackPorch = BreezeAway + ColourBurst + WaitToData
waitvid COLOR_SYNC, #0
mov VSCL, _VSCL_FrontPorch
waitvid COLOR_SYNC, #0
mov R1, _VSCL_SynchronizingPulse
sub R1, _VSCL_FrontPorch
mov VSCL, R1
' mov VSCL, _VSCL_SynchronizingPulse
waitvid COLOR_BLACK, #0
djnz R0, #:Loop
VSyncLow_Ret ret
' ─────────────────────────────────────────
' Utility subroutines
' ─────────────────────────────────────────
' Divide R1 by R2 and return the result in R3 with 32 bits of decimal precision
' Input: R1 → Dividend
' R2 → Divisor (it is required that R1 < R2)
' Output: R3 → (R1/R2) << 32
Divide mov R0, #33
:Loop cmpsub R1, R2 wc
rcl R3, #1
shl R1, #1
djnz R0, #:Loop
Divide_Ret ret
phaseflip long $00000000
phasemask long $F0F0F0F0 ' invert hue portion of color
' ─────────────────────────────────────────
' Uninitialized data
' ─────────────────────────────────────────
R0 res 1
R1 res 1
R2 res 1
R3 res 1
R4 res 1
R5 res 1
R6 res 1
COLORS res 1
PIXELS res 1
_VCFG_PinMask res 1
_VCFG_PinGroup res 1
_VCFG_Mode res 1
_PortMask res 1
_ClockFreq res 1
_VSCL_Character res 1
_VSCL_VisibleLine res 1
_VSCL_HalfLine res 1
_VSCL_LeftBorder res 1
_VSCL_RightBorder res 1
_VSCL_FrontPorch res 1
_VSCL_SynchronizingPulse res 1
_VSCL_BreezeAway res 1
_VSCL_ColourBurst res 1
_VSCL_BackPorch res 1
_VSCL_WaitToData res 1
_VerticalLines res 1
_TopLine res 1
_BottomLine res 1
_Burst1 res 1
_Burst2 res 1
LineCounter res 1
CharacterRows res 1
TilePtr res 1
AttribPtr res 1
ScreenPtr res 1
StatusPtr res 1
VSyncPtr res 1
PAddr res 1
PCtr res 1
OffsetX res 1
OffsetY res 1
Status res 1
VideoMode res 1
fit