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 /tools | |
| 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.
Diffstat (limited to '')
| -rw-r--r-- | tools/genfont.py | 105 | ||||
| -rw-r--r-- | tools/ppmview.py | 16 |
2 files changed, 121 insertions, 0 deletions
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)) |
