; ============================================================================= ; 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 - now with lowercase!", 0 .s1 db "The quick brown fox jumps over a", 0 .s2 db "lazy dog. Digits 0123456789 too.", 0 .s3 db "abcdefghijklmnopqrstuvwxyz", 0 .s4 db "ascenders bdfhklt, descenders gjpqy", 0 .s5 db "Punct .,:;!? +-*/=<>()[]#@ ok", 0