aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-18 21:50:22 +0200
committeruser <user@clank>2026-07-18 21:50:22 +0200
commit9b813f2a0eb8626f84489de0f55dc6da2ed22420 (patch)
tree0887bb78ecc91a1c63fc5ed76be10adc9b8c2743
parentterm: 3 more renderer wins - 10.9x scroll, 2.7x putc vs pre-cache (diff)
downloadgbos-9b813f2a0eb8626f84489de0f55dc6da2ed22420.tar.gz
gbos-9b813f2a0eb8626f84489de0f55dc6da2ed22420.tar.xz
gbos-9b813f2a0eb8626f84489de0f55dc6da2ed22420.zip
term: SCY ring-scroll - scroll writes ONE map row; OSK toggle is 212 cycles
The BG map's 32 rows become a ring: logical row L always lives at map row (wMapTop + L) & 31, and SCY = (wMapTop + wViewTop)*8 places the view. Scrolling bumps wMapTop, rebuilds the one new bottom line and writes its single map row (map_row_one); the other 17 rows move for free. The whole OSK view dance - shift the cursor above the keys, restore the backlog on hide - is now term_view_update: one SCY write, no map rewrite at all. OSK safety: the keyboard lives on the WINDOW layer, which SCY never moves. Stale ring rows can only appear under it: wViewTop = max(0, wCurRow-14) <= 3 is nonzero only while the OSK is visible, and the window covers exactly those bottom 3 rows (WX=7, WY=120). Verified: scroll + view shift + scroll-while-OSK-up + backlog restore all pixel-correct via VRAM screenshots; full README demo passes. term_scroll: 78.5k -> 35.6k T-cycles (857k pre-optimization: 24x). OSK toggle: 45k tilemap rewrite -> 212 cycles. Regenerated demo/gbos-demo.gif on the new renderer.
Diffstat (limited to '')
-rw-r--r--demo/gbos-demo.gifbin511818 -> 350160 bytes
-rw-r--r--src/osk.asm4
-rw-r--r--src/term.asm185
3 files changed, 112 insertions, 77 deletions
diff --git a/demo/gbos-demo.gif b/demo/gbos-demo.gif
index 0e000a2..754477d 100644
--- a/demo/gbos-demo.gif
+++ b/demo/gbos-demo.gif
Binary files differ
diff --git a/src/osk.asm b/src/osk.asm
index d5edad7..0f188eb 100644
--- a/src/osk.asm
+++ b/src/osk.asm
@@ -247,12 +247,12 @@ osk_toggle::
ld a, [rLCDC]
or %00100000
ld [rLCDC], a
- jp term_write_tilemap
+ jp term_view_update ; SCY-only now: the map ring is untouched
.hide
ld a, [rLCDC]
and %11011111
ld [rLCDC], a
- jp term_write_tilemap ; offset recomputes to 0 -> full backlog
+ jp term_view_update ; offset recomputes to 0 -> full backlog
; movement with wrap-around; each moves the highlight
osk_up:
diff --git a/src/term.asm b/src/term.asm
index fbeaea3..6a94024 100644
--- a/src/term.asm
+++ b/src/term.asm
@@ -35,6 +35,7 @@ DEF ACACHE EQU 40 ; attr cache in COL_BUF's spare stride bytes: the tile
; terminal state (WRAMX gap $D580-$D5FF)
DEF wVram EQU $D584 ; 2
+DEF wMapTop EQU $D5BB ; BG-map ring: tilemap row where logical row 0 lives
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?
@@ -88,6 +89,7 @@ term_init::
ld [wCurRow], a
ld [wCurCol], a
ld [wViewTop], a ; view starts unshifted (all 18 rows)
+ ld [wMapTop], a ; ring starts at map row 0
ld [wOskVisible], a ; OSK hidden until toggled
ld [wAnsiState], a ; ANSI parser idle
ld a, $FF
@@ -128,39 +130,105 @@ term_init::
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).
+; The BG map is a 32-row RING. Logical row L always lives at tilemap row
+; (wMapTop + L) & 31; SCY = (wMapTop + wViewTop)*8 places the view. So:
+; scroll = bump wMapTop, write ONE new map row, set SCY
+; OSK toggle / view shift = set SCY only (the OSK itself is on the WINDOW
+; layer, which SCY never moves; stale ring rows can only show under the
+; window: wViewTop = max(0, wCurRow-14) <= 3 is nonzero only while the
+; OSK is visible, and the OSK covers exactly those bottom 3 rows)
+; -----------------------------------------------------------------------------
+
+; -----------------------------------------------------------------------------
+; term_write_tilemap - (re)write every logical row's map row + place the view.
+; Rare: init / full refresh only; scroll and OSK toggling take the fast paths.
; -----------------------------------------------------------------------------
term_write_tilemap::
+ xor a
+.rows
+ push af
+ call map_row_one
+ pop af
+ inc a
+ cp TROWS
+ jr c, .rows
+ ; fall through into term_view_update
+
+; -----------------------------------------------------------------------------
+; term_view_update - place the view: SCY = (wMapTop + wViewTop)*8. The whole
+; OSK view dance (cursor above the keys, backlog restore on hide) is one
+; register write now - no map rewrite.
+; -----------------------------------------------------------------------------
+term_view_update::
call compute_offset
+ ld a, [wMapTop]
+ ld hl, wViewTop
+ add [hl]
+ and 31
+ add a
+ add a
+ add a ; *8 px
+ ld [rSCY], a
+ ret
+
+; -----------------------------------------------------------------------------
+; map_row_one - write the BG-map row for logical row A (0..17): tile indices
+; (VBK 0), then palette attrs from the cache (VBK 1), split at the pos-256
+; VRAM-bank boundary into two tight runs.
+; -----------------------------------------------------------------------------
+map_row_one:
+ ld c, a ; C = logical row
+ ; slot = assign[L] -> wTMSlot
+ add LOW(wAssign)
+ ld l, a
+ ld a, HIGH(wAssign)
+ adc 0
+ ld h, a
+ ld a, [hl]
+ ld [wTMSlot], a
+ ; DE = pos = slot*20
+ ld l, a
+ ld h, 0
+ add hl, hl
+ add hl, hl ; *4
+ ld d, h
+ ld e, l
+ add hl, hl
+ add hl, hl ; *16
+ add hl, de ; *20
+ ld d, h
+ ld e, l ; DE = pos start
+ ; HL = $9800 + ((wMapTop + L) & 31) * 32 (the ring row)
+ ld a, [wMapTop]
+ add c
+ and 31
+ ld l, a
+ ld h, 0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl ; *32
+ ld a, h
+ add $98
+ ld h, a
+ ; ---- indices (VBK 0): 20 bytes of LOW(pos); E wraps naturally ----
+ push hl ; row addr again for the attr pass
+ push de ; pos again for the bank split
xor a
ld [rVBK], a
- ld c, 0 ; screen row
-.i_row
- call .slot20 ; DE = assign[c]*20 (pos start), HL = tilemap row
ld b, TCOLS/2
-.i_col
+.idx
ld a, e
- ld [hl+], a ; index = LOW(pos)
+ ld [hl+], a
inc de
dec b
- jr nz, .i_col
- inc c
- ld a, c
- cp TROWS
- jr c, .i_row
-
+ jr nz, .idx
+ pop de
+ pop hl
+ ; ---- attrs (VBK 1): k = tiles still in bank 0, then two cache runs ----
ld a, 1
ld [rVBK], a
- xor a
- ld [wRT_sr], a ; screen row (render_tile scratch, free here)
-.a_row
- ld a, [wRT_sr]
- ld c, a
- call .slot20 ; DE = pos start, HL = tilemap addr, wTMSlot set
- ; Split the row at the pos-256 VRAM-bank boundary: the first k tiles are
- ; bank 0 (attr = cache byte), the rest bank 1 (attr | $08). Each run is
- ; then a tight cache->tilemap copy: no per-tile addressing or bank test.
ld a, d
or a
jr nz, .k0 ; pos >= 256: whole row is bank 1
@@ -176,7 +244,7 @@ term_write_tilemap::
xor a
.khave
ld [wRT_tcol], a ; k = bank-0 tiles in this row
- ; DE = &cache[slot][0] (pos isn't needed beyond the split above)
+ ; DE = &cache[slot][0]
push hl
ld a, [wTMSlot]
ld l, a
@@ -209,7 +277,7 @@ term_write_tilemap::
ld b, a
ld a, TCOLS/2
sub b
- jr z, .arnext
+ jr z, .done
ld b, a
.r2
ld a, [de]
@@ -218,53 +286,10 @@ term_write_tilemap::
ld [hl+], a
dec b
jr nz, .r2
-.arnext
- ld a, [wRT_sr]
- inc a
- ld [wRT_sr], a
- cp TROWS
- jr c, .a_row
+.done
xor a
ld [rVBK], a
ret
-; helper: C = screen row -> DE = assign[C]*20, HL = $9800 + C*32
-.slot20
- ld a, [wViewTop]
- add c ; logical row = screen row + view offset
- cp TROWS
- jr c, .lok
- ld a, TROWS-1 ; rows the OSK covers clamp to the cursor line
-.lok
- add LOW(wAssign)
- ld l, a
- ld a, HIGH(wAssign)
- adc 0
- ld h, a
- ld a, [hl] ; L = assign[logical row]
- ld [wTMSlot], a ; stash slot for the color-aware attr pass
- 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
; -----------------------------------------------------------------------------
; slot_addr - A = screen row -> HL = TERM_BUF + assign[row]*64
@@ -735,11 +760,13 @@ update_cursor_attr:
or $08
.noattr_bank
ld c, a ; C = attribute byte
- ; physical row = wCurRow - wViewTop
+ ; ring row = (wMapTop + wCurRow) & 31 - the cursor's map row is fixed by
+ ; its logical row; the view offset only moves SCY, never the map.
ld a, [wCurRow]
- ld hl, wViewTop
- sub [hl]
- ; tilemap attr addr = $9800 + physical*32 + (wCurCol/2), VRAM bank 1
+ ld hl, wMapTop
+ add [hl]
+ and 31
+ ; tilemap attr addr = $9800 + ringrow*32 + (wCurCol/2), VRAM bank 1
ld l, a
ld h, 0
add hl, hl
@@ -853,8 +880,16 @@ term_scroll::
ld a, c
cp TCOLS/2
jr c, .build
- call term_write_tilemap
- ret
+ ; ring-scroll: every logical row moved up one, so bump the map top, write
+ ; the ONE new bottom map row, and let SCY do the other 17 (the OSK window
+ ; is untouched - SCY doesn't move the window layer).
+ ld a, [wMapTop]
+ inc a
+ and 31
+ ld [wMapTop], a
+ ld a, TROWS-1
+ call map_row_one
+ jp term_view_update
; cursor_down - advance the cursor row, scrolling when it runs past the bottom.
cursor_down:
@@ -872,7 +907,7 @@ cursor_down:
ld a, [wOskVisible]
or a
ret z
- jp term_write_tilemap
+ jp term_view_update ; SCY-only: no map rewrite
; compute_offset - set wViewTop so the cursor line is always on screen: 0 when
; the OSK is hidden; otherwise max(0, wCurRow - 14) so the cursor sits at the