diff options
| author | user <user@clank> | 2026-07-16 13:54:23 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-16 13:54:23 +0200 |
| commit | 4656a1003226709e9b3c804ec32b4913fca4c7f4 (patch) | |
| tree | 516185fb1827495f0fe747b6365eb963ad5cf031 | |
| parent | fs: reject open()/read() on a directory (EISDIR) (diff) | |
| download | gbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.tar.gz gbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.tar.xz gbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.zip | |
term: 40-column text terminal on the CGB LCD (4x8 dynamic tiles)
Add a background-tile text terminal that packs TWO 4px-wide characters
into each 8x8 tile, giving a 40x18 grid instead of the 20x18 you'd get
from an 8x8 font. The tilemap is static (one dedicated VRAM tile per
screen position); we rebuild a tile's 16 bytes from two glyphs whenever
a character changes. 360 tiles exceed the 256 a single tilemap can
address, so positions 256-359 live in VRAM bank 1 via the CGB tilemap
attribute bank-bit -- hence CGB-only.
- Switch the ROM to CGB (rgbfix -C). Safe for the FS: every process has
PROC_WRAMB=1, so SVBK stays on bank 1 and the WRAMX FS caches don't move.
- CGB BG palette 0 = white bg / black text via BCPS/BCPD.
- src/term.asm: term_init (palette + static tilemap + clear buffer),
build_tile (combine two 4px glyphs -> one 8x8 tile, correct VRAM bank),
term_redraw (rebuild all tiles), term_puts, term_show, term_test.
- src/font.asm: 96-glyph 3x5-in-4x8 font, generated by tools/genfont.py.
- 40x18 text buffer at $D600 (WRAMX, above the FS caches).
- tools/ppmview.py: render a --shot PPM as ASCII so the LCD is inspectable
from the shell during development.
Verified via emulator screenshot: "GBOS TERMINAL", the alphabet, and a
bank-1 row all render legibly at 40 columns. Serial shell + filesystem
still work unchanged.
Note: a full term_redraw builds 360 tiles and takes ~8 frames; fine for
incremental single-char updates, but scrolling needs a smarter path
(next milestone). Input (no keyboard) is also still TODO.
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | src/boot.asm | 2 | ||||
| -rw-r--r-- | src/font.asm | 99 | ||||
| -rw-r--r-- | src/term.asm | 381 | ||||
| -rw-r--r-- | tools/genfont.py | 105 | ||||
| -rw-r--r-- | tools/ppmview.py | 16 |
6 files changed, 604 insertions, 1 deletions
@@ -8,7 +8,7 @@ OBJS := $(patsubst src/%.asm,$(BUILD)/%.o,$(SRCS)) ASFLAGS := -I include -Wall # MBC5 + RAM + battery; -r 3 = 32 KiB cart RAM (bump for more task banks) -FIXFLAGS := -v -m MBC5+RAM+BATTERY -r 4 -p 0xFF -t GBOS +FIXFLAGS := -v -C -m MBC5+RAM+BATTERY -r 4 -p 0xFF -t GBOS .PHONY: all clean run diff --git a/src/boot.asm b/src/boot.asm index 9d9571e..0a5f2e3 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -56,6 +56,8 @@ KernelInit: ld a, HIGH(wKernelPCB) ld [wCurProc+1], a call fs_init ; mount the disk (format on first boot; persists) + call term_init ; set up the LCD text terminal + call term_test ; render a test pattern ; --- spawn the init task (pid 1); it fork()s the rest --- ld hl, TaskInit diff --git a/src/font.asm b/src/font.asm new file mode 100644 index 0000000..a6ff7d9 --- /dev/null +++ b/src/font.asm @@ -0,0 +1,99 @@ +; auto-generated by tools/genfont.py - 4x8 font (3x5 glyphs), 96 glyphs +SECTION "font", ROM0 +FontData:: + db $00,$00,$00,$00,$00,$00,$00,$00 ; ' ' + db $04,$04,$04,$00,$04,$00,$00,$00 ; '!' + db $0A,$0A,$00,$00,$00,$00,$00,$00 ; '"' + db $0A,$0E,$0A,$0E,$0A,$00,$00,$00 ; '#' + db $04,$06,$0C,$06,$0C,$00,$00,$00 ; '$' + db $0A,$02,$04,$08,$0A,$00,$00,$00 ; '%' + db $04,$0A,$04,$0A,$06,$00,$00,$00 ; '&' + db $04,$04,$00,$00,$00,$00,$00,$00 ; ''' + db $02,$04,$04,$04,$02,$00,$00,$00 ; '(' + db $08,$04,$04,$04,$08,$00,$00,$00 ; ')' + db $0A,$04,$0E,$04,$0A,$00,$00,$00 ; '*' + db $00,$04,$0E,$04,$00,$00,$00,$00 ; '+' + db $00,$00,$00,$04,$08,$00,$00,$00 ; ',' + db $00,$00,$0E,$00,$00,$00,$00,$00 ; '-' + db $00,$00,$00,$00,$04,$00,$00,$00 ; '.' + db $02,$02,$04,$08,$08,$00,$00,$00 ; '/' + db $04,$0A,$0A,$0A,$04,$00,$00,$00 ; '0' + db $04,$0C,$04,$04,$0E,$00,$00,$00 ; '1' + db $0C,$02,$04,$08,$0E,$00,$00,$00 ; '2' + db $0C,$02,$04,$02,$0C,$00,$00,$00 ; '3' + db $0A,$0A,$0E,$02,$02,$00,$00,$00 ; '4' + db $0E,$08,$0C,$02,$0C,$00,$00,$00 ; '5' + db $06,$08,$0C,$0A,$04,$00,$00,$00 ; '6' + db $0E,$02,$04,$04,$04,$00,$00,$00 ; '7' + db $04,$0A,$04,$0A,$04,$00,$00,$00 ; '8' + db $04,$0A,$06,$02,$0C,$00,$00,$00 ; '9' + db $00,$04,$00,$04,$00,$00,$00,$00 ; ':' + db $00,$04,$00,$04,$08,$00,$00,$00 ; ';' + db $02,$04,$08,$04,$02,$00,$00,$00 ; '<' + db $00,$0E,$00,$0E,$00,$00,$00,$00 ; '=' + db $08,$04,$02,$04,$08,$00,$00,$00 ; '>' + db $0C,$02,$04,$00,$04,$00,$00,$00 ; '?' + db $04,$0A,$0E,$08,$06,$00,$00,$00 ; '@' + db $0C,$0A,$0E,$0A,$0A,$00,$00,$00 ; 'A' + db $0C,$0A,$0C,$0A,$0C,$00,$00,$00 ; 'B' + db $06,$08,$08,$08,$06,$00,$00,$00 ; 'C' + db $0C,$0A,$0A,$0A,$0C,$00,$00,$00 ; 'D' + db $0E,$08,$0C,$08,$0E,$00,$00,$00 ; 'E' + db $0E,$08,$0C,$08,$08,$00,$00,$00 ; 'F' + db $06,$08,$0A,$0A,$06,$00,$00,$00 ; 'G' + db $0A,$0A,$0E,$0A,$0A,$00,$00,$00 ; 'H' + db $0E,$04,$04,$04,$0E,$00,$00,$00 ; 'I' + db $02,$02,$02,$0A,$04,$00,$00,$00 ; 'J' + db $0A,$0A,$0C,$0A,$0A,$00,$00,$00 ; 'K' + db $08,$08,$08,$08,$0E,$00,$00,$00 ; 'L' + db $0A,$0E,$0E,$0A,$0A,$00,$00,$00 ; 'M' + db $0A,$0E,$0E,$0E,$0A,$00,$00,$00 ; 'N' + db $04,$0A,$0A,$0A,$04,$00,$00,$00 ; 'O' + db $0C,$0A,$0C,$08,$08,$00,$00,$00 ; 'P' + db $04,$0A,$0A,$04,$02,$00,$00,$00 ; 'Q' + db $0C,$0A,$0C,$0A,$0A,$00,$00,$00 ; 'R' + db $06,$08,$04,$02,$0C,$00,$00,$00 ; 'S' + db $0E,$04,$04,$04,$04,$00,$00,$00 ; 'T' + db $0A,$0A,$0A,$0A,$06,$00,$00,$00 ; 'U' + db $0A,$0A,$0A,$04,$04,$00,$00,$00 ; 'V' + db $0A,$0A,$0E,$0E,$0A,$00,$00,$00 ; 'W' + db $0A,$0A,$04,$0A,$0A,$00,$00,$00 ; 'X' + db $0A,$0A,$04,$04,$04,$00,$00,$00 ; 'Y' + db $0E,$02,$04,$08,$0E,$00,$00,$00 ; 'Z' + db $0C,$08,$08,$08,$0C,$00,$00,$00 ; '[' + db $08,$08,$04,$02,$02,$00,$00,$00 ; '?' + db $06,$02,$02,$02,$06,$00,$00,$00 ; ']' + db $04,$0A,$00,$00,$00,$00,$00,$00 ; '^' + db $00,$00,$00,$00,$0E,$00,$00,$00 ; '_' + db $08,$04,$00,$00,$00,$00,$00,$00 ; '`' + db $0C,$0A,$0E,$0A,$0A,$00,$00,$00 ; 'a' + db $0C,$0A,$0C,$0A,$0C,$00,$00,$00 ; 'b' + db $06,$08,$08,$08,$06,$00,$00,$00 ; 'c' + db $0C,$0A,$0A,$0A,$0C,$00,$00,$00 ; 'd' + db $0E,$08,$0C,$08,$0E,$00,$00,$00 ; 'e' + db $0E,$08,$0C,$08,$08,$00,$00,$00 ; 'f' + db $06,$08,$0A,$0A,$06,$00,$00,$00 ; 'g' + db $0A,$0A,$0E,$0A,$0A,$00,$00,$00 ; 'h' + db $0E,$04,$04,$04,$0E,$00,$00,$00 ; 'i' + db $02,$02,$02,$0A,$04,$00,$00,$00 ; 'j' + db $0A,$0A,$0C,$0A,$0A,$00,$00,$00 ; 'k' + db $08,$08,$08,$08,$0E,$00,$00,$00 ; 'l' + db $0A,$0E,$0E,$0A,$0A,$00,$00,$00 ; 'm' + db $0A,$0E,$0E,$0E,$0A,$00,$00,$00 ; 'n' + db $04,$0A,$0A,$0A,$04,$00,$00,$00 ; 'o' + db $0C,$0A,$0C,$08,$08,$00,$00,$00 ; 'p' + db $04,$0A,$0A,$04,$02,$00,$00,$00 ; 'q' + db $0C,$0A,$0C,$0A,$0A,$00,$00,$00 ; 'r' + db $06,$08,$04,$02,$0C,$00,$00,$00 ; 's' + db $0E,$04,$04,$04,$04,$00,$00,$00 ; 't' + db $0A,$0A,$0A,$0A,$06,$00,$00,$00 ; 'u' + db $0A,$0A,$0A,$04,$04,$00,$00,$00 ; 'v' + db $0A,$0A,$0E,$0E,$0A,$00,$00,$00 ; 'w' + db $0A,$0A,$04,$0A,$0A,$00,$00,$00 ; 'x' + db $0A,$0A,$04,$04,$04,$00,$00,$00 ; 'y' + db $0E,$02,$04,$08,$0E,$00,$00,$00 ; 'z' + db $02,$04,$0C,$04,$02,$00,$00,$00 ; '{' + db $04,$04,$04,$04,$04,$00,$00,$00 ; '|' + db $08,$04,$06,$04,$08,$00,$00,$00 ; '}' + db $00,$06,$0C,$00,$00,$00,$00,$00 ; '~' + db $0E,$0A,$0A,$0A,$0E,$00,$00,$00 ; '?' diff --git a/src/term.asm b/src/term.asm new file mode 100644 index 0000000..9d13b80 --- /dev/null +++ b/src/term.asm @@ -0,0 +1,381 @@ +; ============================================================================= +; term.asm - a 40x18 text terminal on the CGB LCD. +; +; 4x8 font: each 8x8 background tile holds TWO 4-px characters (left+right). +; The tilemap is static - one dedicated VRAM tile per screen tile-position - +; and we (re)build the 16 tile bytes when a character changes. 360 tiles: +; positions 0-255 in VRAM bank 0, 256-359 in bank 1 (via the tilemap attribute +; bank bit), which is why this is CGB-only. +; ============================================================================= +INCLUDE "include/gbos.inc" + +DEF rSCY EQU $FF42 +DEF rSCX EQU $FF43 +DEF rVBK EQU $FF4F ; CGB VRAM bank select +DEF rBCPS EQU $FF68 ; CGB BG palette index +DEF rBCPD EQU $FF69 ; CGB BG palette data + +DEF TCOLS EQU 40 ; text columns +DEF TROWS EQU 18 ; text rows +DEF TSTRIDE EQU 64 ; bytes per row in the buffer (power of 2 for addressing) +DEF TERM_BUF EQU $D600 ; 18*64 = 1152 B (WRAMX, above the FS caches) + +; terminal scratch (WRAMX gap $D500-$D5FF) +DEF wGlyphL EQU $D580 +DEF wGlyphR EQU $D582 +DEF wVram EQU $D584 + +SECTION "term", ROM0 + +; ----------------------------------------------------------------------------- +; row_addr - A = row -> HL = TERM_BUF + row*64 +; ----------------------------------------------------------------------------- +row_addr: + ld h, 0 + ld l, a + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; *64 + ld de, TERM_BUF + add hl, de + ret + +; ----------------------------------------------------------------------------- +; term_init - CGB palette, static tilemap, clear buffer, LCD on. +; ----------------------------------------------------------------------------- +term_init:: + ; LCD off (so VRAM is freely writable) + xor a + ld [rLCDC], a + ld [rSCX], a + ld [rSCY], a + + ; BG palette 0: color0 = white (bg), color1 = black (text) + ld a, $80 + ld [rBCPS], a ; index 0, auto-increment + ld a, $FF + ld [rBCPD], a ; c0 lo (white $7FFF) + ld a, $7F + ld [rBCPD], a ; c0 hi + xor a + ld [rBCPD], a ; c1 lo (black) + ld [rBCPD], a ; c1 hi + ld [rBCPD], a ; c2..c3 = black + ld [rBCPD], a + ld [rBCPD], a + ld [rBCPD], a + + ; --- tilemap indices (VRAM bank 0): [pos] = LOW(pos) --- + xor a + ld [rVBK], a + ld de, 0 ; pos + ld c, 0 ; row +.mi_row + ; compute tilemap row start = $9800 + row*32 + ld a, c + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; row*32 + ld a, h + add $98 + ld h, a ; HL = $9800 + row*32 + ld b, TCOLS/2 ; 20 tile columns +.mi_col + ld a, e + ld [hl+], a ; index = LOW(pos) + inc de + dec b + jr nz, .mi_col + inc c + ld a, c + cp TROWS + jr c, .mi_row + + ; --- tilemap attributes (VRAM bank 1): bank bit for pos>=256 --- + ld a, 1 + ld [rVBK], a + ld de, 0 + ld c, 0 +.ma_row + ld a, c + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + ld a, h + add $98 + ld h, a + ld b, TCOLS/2 +.ma_col + ld a, d ; HIGH(pos): 0 for 0-255, 1 for 256+ + or a + jr z, .ma_bank0 + ld a, $08 ; VRAM bank 1 for this tile + jr .ma_put +.ma_bank0 + xor a +.ma_put + ld [hl+], a + inc de + dec b + jr nz, .ma_col + inc c + ld a, c + cp TROWS + jr c, .ma_row + xor a + ld [rVBK], a + + ; clear the text buffer to spaces + ld hl, TERM_BUF + ld de, TROWS * TSTRIDE +.clr + ld a, $20 ; ' ' + ld [hl+], a + dec de + ld a, d + or e + jr nz, .clr + + ret + +; term_show - rebuild all tiles then turn the LCD on. +term_show:: + call term_redraw + ld a, %10010001 ; LCD on, BG on, tile data $8000, map $9800 + ld [rLCDC], a + ret + +; ----------------------------------------------------------------------------- +; build_tile - build the 8x8 tile for screen tile-position DE (E=index within +; bank, D=bank 0/1) from characters B (left) and C (right). +; ----------------------------------------------------------------------------- +build_tile: + ld a, d + ld [rVBK], a ; select VRAM bank for this tile + ; VRAM dest = $8000 + E*16 + ld h, 0 + ld l, e + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; E*16 + ld a, h + add $80 + ld h, a ; HL = $8000 + E*16 + ld a, l + ld [wVram], a + ld a, h + ld [wVram+1], a + ; glyphL = FontData + (clamp(B)-32)*8 + ld a, b + call glyph_ptr + ld a, l + ld [wGlyphL], a + ld a, h + ld [wGlyphL+1], a + ld a, c + call glyph_ptr + ld a, l + ld [wGlyphR], a + ld a, h + ld [wGlyphR+1], a + ; 8 rows: dest[2r] = (L[r]<<4)|R[r] ; dest[2r+1] = 0 + ld b, 8 ; row counter + ld a, [wVram] + ld l, a + ld a, [wVram+1] + ld h, a ; HL = VRAM dest +.brow + push hl ; save VRAM ptr + ; left glyph byte + ld a, [wGlyphL] + ld e, a + ld a, [wGlyphL+1] + ld d, a + ld a, [de] + swap a + and $F0 + ld c, a ; left nibble -> high + ; right glyph byte + ld a, [wGlyphR] + ld e, a + ld a, [wGlyphR+1] + ld d, a + ld a, [de] + and $0F + or c ; A = combined row + pop hl + ld [hl+], a ; plane 0 + xor a + ld [hl+], a ; plane 1 = 0 + ; advance both glyph pointers + ld a, [wGlyphL] + inc a + ld [wGlyphL], a + jr nz, .nl + ld a, [wGlyphL+1] + inc a + ld [wGlyphL+1], a +.nl + ld a, [wGlyphR] + inc a + ld [wGlyphR], a + jr nz, .nr + ld a, [wGlyphR+1] + inc a + ld [wGlyphR+1], a +.nr + dec b + jr nz, .brow + ret + +; glyph_ptr - A = char -> HL = FontData + (clamp(A,32,127)-32)*8 +glyph_ptr: + cp 32 + jr nc, .ok + ld a, 32 +.ok + cp 128 + jr c, .ok2 + ld a, 32 +.ok2 + sub 32 + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl ; *8 + ld de, FontData + add hl, de + ret + +; ----------------------------------------------------------------------------- +; term_redraw - rebuild every tile from the text buffer. (LCD should be off, or +; call during VBlank for a full frame.) +; ----------------------------------------------------------------------------- +term_redraw:: + ld c, 0 ; row +.row + push bc + ld a, c + call row_addr ; HL = &buffer[row][0] + ; iterate 20 tile columns + ld b, 0 ; tile column +.col + push bc + push hl + ; DE = pos = row*20 + tcol : maintain via a running counter is simpler, but + ; recompute here: pos = C(row)*20 + B(tcol) + ld a, c + ld l, a + ld h, 0 + add hl, hl + add hl, hl ; row*4 + ld d, h + ld e, l + add hl, hl + add hl, hl ; row*16 + add hl, de ; row*20 + ld a, b + add l + ld e, a + ld a, h + adc 0 + ld d, a ; DE = pos + pop hl ; &buffer[row][0] + push hl + ; charL = buffer[row][tcol*2], charR = [+1] + ld a, b + add a ; tcol*2 + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl+] + ld b, a ; charL + ld a, [hl] + ld c, a ; charR + push de + call build_tile ; DE=pos, B=charL, C=charR + pop de + pop hl + pop bc + inc b + ld a, b + cp TCOLS/2 + jr c, .col + xor a + ld [rVBK], a + pop bc + inc c + ld a, c + cp TROWS + jr c, .row + ret + +; ----------------------------------------------------------------------------- +; term_puts - HL = NUL-terminated string, B = row, C = col : write into buffer. +; ----------------------------------------------------------------------------- +term_puts:: + push hl + ld a, b + call row_addr ; HL = &buffer[row] + ld a, c + add l + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &buffer[row][col] + ld d, h + ld e, l ; DE = dest + pop hl ; HL = string +.l + ld a, [hl+] + or a + ret z + ld [de], a + inc de + jr .l + +; ----------------------------------------------------------------------------- +; term_test - fill the screen with a test pattern and show it. +; ----------------------------------------------------------------------------- +term_test:: + ld hl, .s0 + ld bc, $0000 + call term_puts + ld hl, .s1 + ld bc, $0100 + call term_puts + ld hl, .s2 + ld bc, $0200 + call term_puts + ld hl, .s3 + ld bc, $0500 + call term_puts + ld hl, .s4 + ld bc, $0A00 + call term_puts + ld hl, .s5 + ld bc, $1100 + call term_puts + call term_show + ret +.s0 db "GBOS TERMINAL - 40 COLUMNS x 18 ROWS", 0 +.s1 db "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789", 0 +.s2 db "the quick brown fox jumps over the dog.", 0 +.s3 db "0123456789012345678901234567890123456789", 0 +.s4 db "row 10: some text in the middle here...", 0 +.s5 db "LAST ROW 17 - exercises bank-1 tiles!!!", 0 diff --git a/tools/genfont.py b/tools/genfont.py new file mode 100644 index 0000000..7fe7689 --- /dev/null +++ b/tools/genfont.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Generate a 4x8 font (3x5 glyphs, left-aligned in a 4px cell) as RGBDS asm. +# Output: 96 glyphs (ASCII 32..127), 8 bytes each; each byte = one 4-px row, +# bit3=leftmost pixel of the cell, bits 3..1 = the 3px glyph, bit0 = gap. +G = {} +def d(ch, rows): # rows: 5 strings of width 3 ('#'/' ') + G[ch] = rows +# space +d(' ', [" "," "," "," "," "]) +# letters (uppercase; lowercase mapped to these) +d('A',["## ","# #","###","# #","# #"]) +d('B',["## ","# #","## ","# #","## "]) +d('C',[" ##","# ","# ","# "," ##"]) +d('D',["## ","# #","# #","# #","## "]) +d('E',["###","# ","## ","# ","###"]) +d('F',["###","# ","## ","# ","# "]) +d('G',[" ##","# ","# #","# #"," ##"]) +d('H',["# #","# #","###","# #","# #"]) +d('I',["###"," # "," # "," # ","###"]) +d('J',[" #"," #"," #","# #"," # "]) +d('K',["# #","# #","## ","# #","# #"]) +d('L',["# ","# ","# ","# ","###"]) +d('M',["# #","###","###","# #","# #"]) +d('N',["# #","###","###","###","# #"]) +d('O',[" # ","# #","# #","# #"," # "]) +d('P',["## ","# #","## ","# ","# "]) +d('Q',[" # ","# #","# #"," # "," #"]) +d('R',["## ","# #","## ","# #","# #"]) +d('S',[" ##","# "," # "," #","## "]) +d('T',["###"," # "," # "," # "," # "]) +d('U',["# #","# #","# #","# #"," ##"]) +d('V',["# #","# #","# #"," # "," # "]) +d('W',["# #","# #","###","###","# #"]) +d('X',["# #","# #"," # ","# #","# #"]) +d('Y',["# #","# #"," # "," # "," # "]) +d('Z',["###"," #"," # ","# ","###"]) +# digits +d('0',[" # ","# #","# #","# #"," # "]) +d('1',[" # ","## "," # "," # ","###"]) +d('2',["## "," #"," # ","# ","###"]) +d('3',["## "," #"," # "," #","## "]) +d('4',["# #","# #","###"," #"," #"]) +d('5',["###","# ","## "," #","## "]) +d('6',[" ##","# ","## ","# #"," # "]) +d('7',["###"," #"," # "," # "," # "]) +d('8',[" # ","# #"," # ","# #"," # "]) +d('9',[" # ","# #"," ##"," #","## "]) +# punctuation +d('.',[" "," "," "," "," # "]) +d(',',[" "," "," "," # ","# "]) +d(':',[" "," # "," "," # "," "]) +d(';',[" "," # "," "," # ","# "]) +d('!',[" # "," # "," # "," "," # "]) +d('?',["## "," #"," # "," "," # "]) +d('-',[" "," ","###"," "," "]) +d('+',[" "," # ","###"," # "," "]) +d('=',[" ","###"," ","###"," "]) +d('*',["# #"," # ","###"," # ","# #"]) +d('/',[" #"," #"," # ","# ","# "]) +d('\\',["# ","# "," # "," #"," #"]) +d('(',[" #"," # "," # "," # "," #"]) +d(')',["# "," # "," # "," # ","# "]) +d('[',["## ","# ","# ","# ","## "]) +d(']',[" ##"," #"," #"," #"," ##"]) +d('<',[" #"," # ","# "," # "," #"]) +d('>',["# "," # "," #"," # ","# "]) +d('#',["# #","###","# #","###","# #"]) +d('$',[" # "," ##","## "," ##","## "]) # rough +d('%',["# #"," #"," # ","# ","# #"]) +d('&',[" # ","# #"," # ","# #"," ##"]) +d("'",[" # "," # "," "," "," "]) +d('"',["# #","# #"," "," "," "]) +d('@',[" # ","# #","###","# "," ##"]) +d('_',[" "," "," "," ","###"]) +d('~',[" "," ##","## "," "," "]) +d('^',[" # ","# #"," "," "," "]) +d('|',[" # "," # "," # "," # "," # "]) +d('{',[" #"," # ","## "," # "," #"]) +d('}',["# "," # "," ##"," # ","# "]) +d('`',["# "," # "," "," "," "]) + +def glyph_bytes(rows): + out=[] + # 5 glyph rows in the top; cell is 4px wide, glyph 3px left-aligned (bit3..1) + for r in range(8): + if r < 5: + s = rows[r] + v = 0 + for i,px in enumerate(s): # i=0 leftmost -> bit3 + if px=='#': v |= (1 << (3-i)) + out.append(v) + else: + out.append(0) + return out + +lines=["; auto-generated by tools/genfont.py - 4x8 font (3x5 glyphs), 96 glyphs", + "SECTION \"font\", ROM0", "FontData::"] +for code in range(32,128): + ch = chr(code) + key = ch.upper() if ch.isalpha() else ch + rows = G.get(key, G.get(ch, ["###","# #","# #","# #","###"])) # box for unknown + b = glyph_bytes(rows) + lines.append(" db " + ",".join("$%02X"%x for x in b) + " ; '%s'"%(ch if ch!='\\' and code!=127 else '?')) +open("/home/user/dev/gbos/src/font.asm","w").write("\n".join(lines)+"\n") +print("wrote src/font.inc:", (128-32), "glyphs") diff --git a/tools/ppmview.py b/tools/ppmview.py new file mode 100644 index 0000000..1e54bd4 --- /dev/null +++ b/tools/ppmview.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# Render a PPM screenshot as ASCII so we can "read" the GB LCD. +import sys +def load(path): + f=open(path,'rb'); assert f.readline().strip()==b'P6' + w,h=map(int,f.readline().split()); f.readline() + d=f.read(w*h*3); return w,h,d +w,h,d=load(sys.argv[1] if len(sys.argv)>1 else 'shot.ppm') +y0=int(sys.argv[2]) if len(sys.argv)>2 else 0 +y1=int(sys.argv[3]) if len(sys.argv)>3 else h +for y in range(y0,min(y1,h)): + row=[] + for x in range(w): + i=(y*w+x)*3; b=(d[i]+d[i+1]+d[i+2])//3 + row.append('#' if b<128 else '.') + print(''.join(row)) |
