diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/boot.asm | 2 | ||||
| -rw-r--r-- | src/term.asm | 662 |
2 files changed, 455 insertions, 209 deletions
diff --git a/src/boot.asm b/src/boot.asm index 0a5f2e3..ea52858 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -57,7 +57,7 @@ KernelInit: 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 + call term_demo ; cursor + scroll demo ; --- spawn the init task (pid 1); it fork()s the rest --- ld hl, TaskInit diff --git a/src/term.asm b/src/term.asm index 2b5caf3..deb6748 100644 --- a/src/term.asm +++ b/src/term.asm @@ -1,11 +1,15 @@ ; ============================================================================= -; term.asm - a 40x18 text terminal on the CGB LCD. +; term.asm - a 40x18 text terminal on the CGB LCD, with cursor and fast scroll. ; -; 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. +; 4x8 font: each 8x8 background tile holds TWO 4-px characters (left+right), +; giving 40 columns. Tiles are LINE-BOUND: line-slot L owns the 20 VRAM tiles +; at tile-positions [L*20 .. L*20+19] (index = LOW(pos), VRAM bank = HIGH(pos); +; positions 256-359 live in bank 1 - hence CGB-only). assign[sr] maps each of +; the 18 screen rows to a line-slot, and the tilemap points each screen row at +; that slot's tiles. Scrolling just rotates assign[], rebuilds the one new +; bottom line, and rewrites the tilemap - O(1 line), not O(screen). +; +; The cursor is an underline OR'd into the current cell's tile (wCurMask). ; ============================================================================= INCLUDE "include/gbos.inc" @@ -17,153 +21,218 @@ 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 TSTRIDE EQU 64 ; bytes per line-slot in the buffer (power of 2) 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 +; terminal state (WRAMX gap $D580-$D5FF) +DEF wGlyphL EQU $D580 ; 2 +DEF wGlyphR EQU $D582 ; 2 +DEF wVram EQU $D584 ; 2 +DEF wCurRow EQU $D586 ; cursor screen row 0..17 +DEF wCurCol EQU $D587 ; cursor screen col 0..39 +DEF wCursorOn EQU $D588 ; draw the cursor? +DEF wCurMask EQU $D589 ; underline bits to OR into a tile's last row +DEF wRT_sr EQU $D58A ; render_tile scratch +DEF wRT_tcol EQU $D58B +DEF wRT_cL EQU $D58C +DEF wRT_cR EQU $D58D +DEF wScratch EQU $D58E +DEF wAssign EQU $D590 ; assign[18]: screen row -> line-slot ($D590-$D5A1) -SECTION "term", ROM0 +DEF CUR_L EQU $E0 ; cursor underline, left cell (3px) +DEF CUR_R EQU $0E ; cursor underline, right cell -; ----------------------------------------------------------------------------- -; 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 +SECTION "term", ROM0 ; ----------------------------------------------------------------------------- -; term_init - CGB palette, static tilemap, clear buffer, LCD on. +; term_init - CGB palette, identity assign[], clear buffer, build, LCD on. ; ----------------------------------------------------------------------------- term_init:: - ; LCD off (so VRAM is freely writable) xor a - ld [rLCDC], a + ld [rLCDC], a ; LCD off 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 [rBCPS], a ld a, $FF - ld [rBCPD], a ; c0 lo (white $7FFF) + ld [rBCPD], a ld a, $7F - ld [rBCPD], a ; c0 hi + ld [rBCPD], a 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 ld [rBCPD], a ld [rBCPD], a ld [rBCPD], a - ; --- tilemap indices (VRAM bank 0): [pos] = LOW(pos) --- + ; state: assign[sr]=sr, cursor at 0,0 (on) + ld hl, wAssign + ld b, TROWS + xor a +.mkid + ld [hl+], a + inc a + dec b + jr nz, .mkid + xor a + ld [wCurRow], a + ld [wCurCol], a + ld a, 1 + ld [wCursorOn], a + + ; clear the buffer (all slots) 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 + + call term_write_tilemap + call term_redraw ; build all tiles (blank + cursor) + + ld a, %10010001 ; LCD on, BG on, tile data $8000, map $9800 + ld [rLCDC], a + ret + +; ----------------------------------------------------------------------------- +; term_write_tilemap - point each screen row's tilemap entries at assign[]'s +; line-slot tiles. Pass 1 = indices (bank 0), pass 2 = attributes (bank 1). +; ----------------------------------------------------------------------------- +term_write_tilemap:: 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 c, 0 ; screen row +.i_row + call .slot20 ; DE = assign[c]*20 (pos start), HL = tilemap row + ld b, TCOLS/2 +.i_col ld a, e ld [hl+], a ; index = LOW(pos) inc de dec b - jr nz, .mi_col + jr nz, .i_col inc c ld a, c cp TROWS - jr c, .mi_row + jr c, .i_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 +.a_row + call .slot20 ld b, TCOLS/2 -.ma_col - ld a, d ; HIGH(pos): 0 for 0-255, 1 for 256+ +.a_col + ld a, d ; HIGH(pos): 0 or 1 or a - jr z, .ma_bank0 - ld a, $08 ; VRAM bank 1 for this tile - jr .ma_put -.ma_bank0 + jr z, .a0 + ld a, $08 ; VRAM bank 1 attribute + jr .aw +.a0 xor a -.ma_put +.aw ld [hl+], a inc de dec b - jr nz, .ma_col + jr nz, .a_col inc c ld a, c cp TROWS - jr c, .ma_row + jr c, .a_row xor a ld [rVBK], a + ret +; helper: C = screen row -> DE = assign[C]*20, HL = $9800 + C*32 +.slot20 + ld a, c + add LOW(wAssign) + ld l, a + ld a, HIGH(wAssign) + adc 0 + ld h, a + ld a, [hl] ; L = assign[row] + ld l, a + ld h, 0 + add hl, hl + add hl, hl ; L*4 + ld d, h + ld e, l + add hl, hl + add hl, hl ; L*16 + add hl, de ; L*20 + ld d, h + ld e, l ; DE = pos start + 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 + ret - ; 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 - +; ----------------------------------------------------------------------------- +; slot_addr - A = screen row -> HL = TERM_BUF + assign[row]*64 +; ----------------------------------------------------------------------------- +slot_addr: + add LOW(wAssign) + ld l, a + ld a, HIGH(wAssign) + adc 0 + ld h, a + ld a, [hl] ; line-slot + ld l, a + ld h, 0 + 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_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 +; ----------------------------------------------------------------------------- +; 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 ; ----------------------------------------------------------------------------- -; 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 - build tile for tile-position DE (E=index, D=bank) from chars +; B (left) and C (right). ORs wCurMask into the last pixel row (cursor). ; ----------------------------------------------------------------------------- build_tile: ld a, d - ld [rVBK], a ; select VRAM bank for this tile - ; VRAM dest = $8000 + E*16 + ld [rVBK], a ; VRAM bank for this tile ld h, 0 ld l, e add hl, hl @@ -177,7 +246,6 @@ build_tile: 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 @@ -190,15 +258,13 @@ build_tile: 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 + push hl ld a, [wGlyphL] ld e, a ld a, [wGlyphL+1] @@ -206,20 +272,29 @@ build_tile: ld a, [de] swap a and $F0 - ld c, a ; left nibble -> high - ; right glyph byte + ld c, a ; left glyph -> high nibble ld a, [wGlyphR] ld e, a ld a, [wGlyphR+1] ld d, a ld a, [de] and $0F - or c ; A = combined row + or c ; A = combined pixel row + ld e, a ; save (DE reloaded next iter) + ld a, b + dec a + jr nz, .nocur ; last row (b==1)? add cursor underline + ld a, [wCurMask] + or e + jr .wr +.nocur + ld a, e +.wr pop hl ld [hl+], a ; plane 0 xor a ld [hl+], a ; plane 1 = 0 - ; advance both glyph pointers + ; advance glyph pointers - MUST NOT touch HL (it holds the VRAM dest!) ld a, [wGlyphL] inc a ld [wGlyphL], a @@ -240,142 +315,313 @@ build_tile: 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 +; ----------------------------------------------------------------------------- +; render_tile - B = screen row, C = tile column: rebuild that one tile from the +; buffer, adding the cursor underline if the cursor is in it. +; ----------------------------------------------------------------------------- +render_tile: + ld a, b + ld [wRT_sr], a + ld a, c + ld [wRT_tcol], a + ; chars: &buffer[assign[sr]][tcol*2] + ld a, b + call slot_addr ; HL = &buffer[slot][0] + ld a, [wRT_tcol] + add a ; tcol*2 + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl+] + ld [wRT_cL], a + ld a, [hl] + ld [wRT_cR], a + ; cursor mask + xor a + ld [wCurMask], a + ld a, [wCursorOn] + or a + jr z, .pos + ld a, [wCurRow] + ld b, a + ld a, [wRT_sr] + cp b + jr nz, .pos + ld a, [wRT_tcol] + add a ; left cell col + ld b, a + ld a, [wCurCol] + cp b + jr nz, .cr + ld a, CUR_L + ld [wCurMask], a + jr .pos +.cr + inc b ; right cell col + cp b + jr nz, .pos + ld a, CUR_R + ld [wCurMask], a +.pos + ; pos = assign[sr]*20 + tcol -> DE (D=bank, E=index) + ld a, [wRT_sr] + add LOW(wAssign) + ld l, a + ld a, HIGH(wAssign) + adc 0 + ld h, a + ld a, [hl] ld l, a ld h, 0 add hl, hl add hl, hl - add hl, hl ; *8 - ld de, FontData - add hl, de + ld d, h + ld e, l ; L*4 + add hl, hl + add hl, hl + add hl, de ; L*20 + ld a, [wRT_tcol] + add l + ld l, a + ld a, h + adc 0 + ld h, a ; HL = pos + ld d, h + ld e, l ; DE = pos + ld a, [wRT_cL] + ld b, a + ld a, [wRT_cR] + ld c, a + call build_tile + xor a + ld [rVBK], a ret +; render_cursor_tile - rebuild the tile at the cursor position. +render_cursor_tile: + ld a, [wCurRow] + ld b, a + ld a, [wCurCol] + srl a ; /2 = tile col + ld c, a + jp render_tile + ; ----------------------------------------------------------------------------- -; term_redraw - rebuild every tile from the text buffer. (LCD should be off, or -; call during VBlank for a full frame.) +; term_redraw - rebuild every tile from the buffer (init / full refresh). ; ----------------------------------------------------------------------------- term_redraw:: - ld c, 0 ; row + ld b, 0 ; screen row .row - push bc - ld a, c - call row_addr ; HL = &buffer[row][0] - ; iterate 20 tile columns - ld b, 0 ; tile column + ld c, 0 ; tile col .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) + call render_tile + pop bc + inc c ld a, c + cp TCOLS/2 + jr c, .col + inc b + ld a, b + cp TROWS + jr c, .row + ret + +; ----------------------------------------------------------------------------- +; term_scroll - scroll up one line: rotate assign[], blank+build the new bottom +; line, rewrite the tilemap. +; ----------------------------------------------------------------------------- +term_scroll:: + ld a, [wAssign] + ld [wScratch], a ; freed line-slot + ld hl, wAssign+1 + ld de, wAssign + ld b, TROWS-1 +.rot + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .rot + ld a, [wScratch] + ld [wAssign + TROWS-1], a ; new bottom row uses the freed slot + ; clear that slot to spaces 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 hl, hl + add hl, hl + add hl, hl + add hl, hl ; *64 + ld de, TERM_BUF + add hl, de + ld b, TCOLS +.clr + ld a, $20 + ld [hl+], a + dec b + jr nz, .clr + ; rebuild bottom line tiles (screen row 17) + ld c, 0 +.build + push bc + ld b, TROWS-1 + call render_tile + pop bc + inc c + ld a, c + cp TCOLS/2 + jr c, .build + call term_write_tilemap + ret + +; cursor_down - advance cursor row, scrolling if it runs off the bottom. +cursor_down: + ld a, [wCurRow] + inc a + cp TROWS + jr c, .ok + call term_scroll + ld a, TROWS-1 +.ok + ld [wCurRow], a + ret + +; ----------------------------------------------------------------------------- +; term_putc - A = character. Print at the cursor, advance, handle \n \r \b. +; ----------------------------------------------------------------------------- +term_putc:: + cp 10 + jr z, .newline + cp 13 + jr z, .cret + cp 8 + jr z, .bs + cp 32 + ret c ; ignore other control chars + ; printable: buffer[assign[cur]][cc] = char + ld [wRT_cL], a + ld a, [wCurRow] + call slot_addr + ld a, [wCurCol] add l - ld e, a + ld l, 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 + ld h, a + ld a, [wRT_cL] + ld [hl], a + ; redraw current cell WITHOUT cursor (shows the char) + xor a + ld [wCursorOn], a + call render_cursor_tile + ; advance + ld a, [wCurCol] + inc a + cp TCOLS + jr c, .adv + xor a + ld [wCurCol], a + call cursor_down + jr .show +.adv + ld [wCurCol], a +.show + ld a, 1 + ld [wCursorOn], a + call render_cursor_tile + ret +.newline + xor a + ld [wCursorOn], a + call render_cursor_tile + xor a + ld [wCurCol], a + call cursor_down + ld a, 1 + ld [wCursorOn], a + call render_cursor_tile + ret +.cret + xor a + ld [wCursorOn], a + call render_cursor_tile + xor a + ld [wCurCol], a + ld a, 1 + ld [wCursorOn], a + call render_cursor_tile + ret +.bs + ld a, [wCurCol] + or a + ret z + xor a + ld [wCursorOn], a + call render_cursor_tile ; clear cursor at old + ld a, [wCurCol] + dec a + ld [wCurCol], a + ; blank the cell we backed into + ld a, [wCurRow] + call slot_addr + ld a, [wCurCol] 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 + ld a, $20 + ld [hl], a + ld a, 1 + ld [wCursorOn], a + call render_cursor_tile ret ; ----------------------------------------------------------------------------- -; term_puts - HL = NUL-terminated string, B = row, C = col : write into buffer. +; term_puts - HL = NUL-terminated string: print via term_putc. ; ----------------------------------------------------------------------------- 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 + push hl + call term_putc + pop hl + jr term_puts ; ----------------------------------------------------------------------------- -; term_test - fill the screen with a test pattern and show it. +; term_demo - drive the terminal: print enough lines to scroll, end at a prompt. ; ----------------------------------------------------------------------------- -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 +term_demo:: + ld hl, .msg 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 +.msg: + db "GBOS terminal: cursor + fast scroll", 10 + db "01 the quick brown fox jumps", 10 + db "02 over the lazy dog.", 10 + db "03 line three of the scroll test", 10 + db "04 line four", 10 + db "05 line five", 10 + db "06 line six", 10 + db "07 line seven", 10 + db "08 line eight", 10 + db "09 line nine", 10 + db "10 line ten", 10 + db "11 line eleven", 10 + db "12 line twelve", 10 + db "13 line thirteen", 10 + db "14 line fourteen", 10 + db "15 line fifteen", 10 + db "16 line sixteen", 10 + db "17 line seventeen", 10 + db "18 line eighteen", 10 + db "19 line nineteen", 10 + db "20 line twenty", 10 + db "21 line twenty-one", 10 + db "22 line twenty-two", 10 + db "ready$ ", 0 |
