1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-12-24 14:28:06 +11:00

New kernel module: grid

This commit is contained in:
Virgil Dupras 2020-02-24 20:36:08 -05:00
parent 434c8d5c0d
commit 247b200dcc
8 changed files with 299 additions and 194 deletions

View File

@ -16,10 +16,13 @@ Launch the emulator with `./sms /path/to/rom` (you can use the binary from the
This will show a window with the screen's content on it. The mappings to the
pad are:
* Arrows
* Z --> A
* X --> B
* C --> C
* S --> Start
* W --> Up
* A --> Left
* S --> Down
* D --> Right
* H --> A
* J --> B
* K --> C
* L --> Start
Press ESC to quit.

View File

@ -180,29 +180,29 @@ void event_loop()
bool ispressed = e->response_type == XCB_KEY_PRESS;
switch (ev->detail) {
case 0x09: return; // ESC
case 0x27: // S
pad_setbtn(&pad, PAD_BTN_START, ispressed);
break;
case 0x34: // Z
pad_setbtn(&pad, PAD_BTN_A, ispressed);
break;
case 0x35: // X
pad_setbtn(&pad, PAD_BTN_B, ispressed);
break;
case 0x36: // C
pad_setbtn(&pad, PAD_BTN_C, ispressed);
break;
case 0x62:
case 0x19: // W
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
break;
case 0x64:
case 0x26: // A
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
break;
case 0x66:
case 0x27: // S
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
break;
case 0x28: // D
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
break;
case 0x68:
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
case 0x2b: // H
pad_setbtn(&pad, PAD_BTN_A, ispressed);
break;
case 0x2c: // J
pad_setbtn(&pad, PAD_BTN_B, ispressed);
break;
case 0x2d: // K
pad_setbtn(&pad, PAD_BTN_C, ispressed);
break;
case 0x2e: // L
pad_setbtn(&pad, PAD_BTN_START, ispressed);
break;
}
break;

228
kernel/grid.asm Normal file
View File

@ -0,0 +1,228 @@
; grid - abstraction for grid-like video output
;
; Collapse OS doesn't support curses-like interfaces: too complicated. However,
; in cases where output don't have to go through a serial interface before
; being displayed, we have usually have access to a grid-like interface.
;
; Direct access to this kind of interface allow us to build an abstraction layer
; that is very much alike curses but is much simpler underneath. This unit is
; this abstraction.
;
; The principle is simple: we have a cell grid of X columns by Y rows and we
; can access those cells by their (X, Y) address. In addition to this, we have
; the concept of an active cursor, which will be indicated visually if possible.
;
; Additionally, this module provides a PutC routine, suitable for plugging into
; stdio.
;
; *** Defines ***
;
; GRID_COLS: Number of columns in the grid
; GRID_ROWS: Number of rows in the grid
; GRID_SETCELL: Pointer to routine that sets cell at row D and column E with
; character in A. If C is nonzero, this cell must be displayed,
; if possible, as the cursor.
;
; *** Consts ***
.equ GRID_SIZE GRID_COLS*GRID_ROWS
; *** Variables ***
; Cursor's column
.equ GRID_CURX GRID_RAMSTART
; Cursor's row
.equ GRID_CURY @+1
; Grid's in-memory buffer of the contents on screen. Because we always push to
; display right after a change, this is almost always going to be a correct
; representation of on-screen display.
; The buffer is organized as a rows of columns. The cell at row Y and column X
; is at GRID_BUF+(Y*GRID_COLS)+X.
.equ GRID_BUF @+1
.equ GRID_RAMEND @+GRID_SIZE
; *** Code ***
gridInit:
xor a
ld b, GRID_RAMEND-GRID_RAMEND
ld hl, GRID_RAMSTART
jp fill
; Place HL at row D and column E in the buffer
; Destroys A
_gridPlaceCell:
ld hl, GRID_BUF
ld a, d
or a
ret z
push de ; --> lvl 1
ld de, GRID_COLS
.loop:
add hl, de
dec a
jr nz, .loop
pop de ; <-- lvl 1
; We're at the proper row, now let's advance to cell
ld a, e
jp addHL
; Push row D in the buffer onto the screen.
gridPushRow:
push af
push bc
push de
push hl
; Cursor off
ld c, 0
ld e, c
call _gridPlaceCell
ld b, GRID_COLS
.loop:
ld a, (hl)
; A, C, D and E have proper values
call GRID_SETCELL
inc hl
inc e
djnz .loop
pop hl
pop de
pop bc
pop af
ret
; Clear row D and push contents to screen
gridClrRow:
push af
push bc
push de
push hl
ld e, 0
call _gridPlaceCell
xor a
ld b, GRID_COLS
call fill
call gridPushRow
pop hl
pop de
pop bc
pop af
ret
gridPushScr:
push de
ld d, GRID_ROWS-1
.loop:
call gridPushRow
dec d
jp p, .loop
pop de
ret
; Set character under cursor to A
gridSetCur:
push de
push hl
push af ; --> lvl 1
ld a, (GRID_CURY)
ld d, a
ld a, (GRID_CURX)
ld e, a
call _gridPlaceCell
pop af \ push af ; <--> lvl 1
ld (hl), a
call GRID_SETCELL
pop af ; <-- lvl 1
pop hl
pop de
ret
; Clear character under cursor
gridClrCur:
push af
ld a, ' '
call gridSetCur
pop af
ret
gridLF:
call gridClrCur
push de
push af
ld a, (GRID_CURY)
call .incA
ld d, a
call gridClrRow
ld (GRID_CURY), a
xor a
ld (GRID_CURX), a
pop af
pop de
ret
.incA:
inc a
cp GRID_ROWS
ret nz ; no rollover
; bottom reached, stay on last line and scroll screen
push hl
push de
push bc
ld de, GRID_BUF
ld hl, GRID_BUF+GRID_COLS
ld bc, GRID_SIZE-GRID_COLS
ldir
pop bc
pop de
pop hl
call gridPushScr
dec a
ret
gridBS:
call gridClrCur
push af
ld a, (GRID_CURX)
or a
jr z, .lineup
dec a
ld (GRID_CURX), a
pop af
ret
.lineup:
; end of line, we need to go up one line. But before we do, are we
; already at the top?
ld a, (GRID_CURY)
or a
jr z, .end
dec a
ld (GRID_CURY), a
ld a, GRID_COLS-1
ld (GRID_CURX), a
.end:
pop af
ret
gridPutC:
cp LF
jr z, gridLF
cp BS
jr z, gridBS
cp ' '
ret c ; ignore unhandled control characters
call gridSetCur
push af ; --> lvl 1
; Move cursor
ld a, (GRID_CURX)
cp GRID_COLS-1
jr z, .incline
; We just need to increase X
inc a
ld (GRID_CURX), a
pop af ; <-- lvl 1
ret
.incline:
; increase line and start anew
call gridLF
pop af ; <-- lvl 1
ret

View File

@ -181,8 +181,7 @@ padGetC:
; no action button pressed, but because our pad status changed, update
; VDP before looping.
ld a, (PAD_SELCHR)
call vdpConv
call vdpSpitC
call gridSetCur
jp padGetC
.return:
ld a, LF

View File

@ -17,22 +17,12 @@
;
.equ VDP_CTLPORT 0xbf
.equ VDP_DATAPORT 0xbe
; *** Variables ***
;
; Row of cursor
.equ VDP_ROW VDP_RAMSTART
; Line of cursor
.equ VDP_LINE @+1
.equ VDP_RAMEND @+1
.equ VDP_COLS 32
.equ VDP_ROWS 24
; *** Code ***
vdpInit:
xor a
ld (VDP_ROW), a
ld (VDP_LINE), a
ld hl, vdpInitData
ld b, vdpInitDataEnd-vdpInitData
ld c, VDP_CTLPORT
@ -107,19 +97,30 @@ vdpInit:
out (VDP_CTLPORT), a
ret
; Spits char set in A at current cursor position. Doesn't move the cursor.
; A is a "sega" char
vdpSpitC:
; Convert ASCII char in A into a tile index corresponding to that character.
; When a character is unknown, returns 0x5e (a '~' char).
vdpConv:
; The font is organized to closely match ASCII, so this is rather easy.
; We simply subtract 0x20 from incoming A
sub 0x20
cp 0x5f
ret c ; A < 0x5f, good
ld a, 0x5e
ret
; grid routine. Sets cell at row D and column E to character A
vdpSetCell:
call vdpConv
; store A away
ex af, af'
push bc
ld b, 0 ; we push rotated bits from VDP_LINE into B so
ld b, 0 ; we push rotated bits from D into B so
; that we'll already have our low bits from the
; second byte we'll send right after.
; Here, we're fitting a 5-bit line, and a 5-bit column on 16-bit, right
; aligned. On top of that, our righmost bit is taken because our target
; cell is 2-bytes wide and our final number is a VRAM address.
ld a, (VDP_LINE)
ld a, d
sla a ; should always push 0, so no pushing in B
sla a ; same
sla a ; same
@ -127,9 +128,9 @@ vdpSpitC:
sla a \ rl b
sla a \ rl b
ld c, a
ld a, (VDP_ROW)
ld a, e
sla a ; A * 2
or c ; bring in two low bits from VDP_LINE into high
or c ; bring in two low bits from D into high
; two bits
out (VDP_CTLPORT), a
ld a, b ; 3 low bits set
@ -142,147 +143,6 @@ vdpSpitC:
out (VDP_DATAPORT), a
ret
vdpPutC:
; Then, let's place our cursor. We need to first send our LSB, whose
; 6 low bits contain our row*2 (each tile is 2 bytes wide) and high
; 2 bits are the two low bits of our line
; special case: line feed, carriage return, back space
cp LF
jr z, vdpLF
cp CR
jr z, vdpCR
cp BS
jr z, vdpBS
push af
; ... but first, let's convert it.
call vdpConv
; and spit it on screen
call vdpSpitC
; Move cursor. The screen is 32x24
ld a, (VDP_ROW)
cp 31
jr z, .incline
; We just need to increase row
inc a
ld (VDP_ROW), a
pop af
ret
.incline:
; increase line and start anew
call vdpCR
call vdpLF
pop af
ret
vdpCR:
call vdpClrPos
push af
xor a
ld (VDP_ROW), a
pop af
ret
vdpLF:
; we don't call vdpClrPos on LF because we expect it to be preceded by
; a CR, which already cleared the pos. If we cleared it now, we would
; clear the first char of the line.
push af
ld a, (VDP_LINE)
call .incA
call vdpClrLine
; Also clear the line after this one
push af ; --> lvl 1
call .incA
call vdpClrLine
pop af ; <-- lvl 1
ld (VDP_LINE), a
pop af
ret
.incA:
inc a
cp 24
ret nz ; no rollover
; bottom reached, roll over to top of screen
xor a
ret
vdpBS:
call vdpClrPos
push af
ld a, (VDP_ROW)
or a
jr z, .lineup
dec a
ld (VDP_ROW), a
pop af
ret
.lineup:
; end of line
ld a, 31
ld (VDP_ROW), a
; we have to go one line up
ld a, (VDP_LINE)
or a
jr z, .nowrap
; We have to wrap to the bottom of the screen
ld a, 24
.nowrap:
dec a
ld (VDP_LINE), a
pop af
ret
; Clear tile under cursor
vdpClrPos:
push af
xor a ; space
call vdpSpitC
pop af
ret
; Clear line number A
vdpClrLine:
; see comments in vdpSpitC for VRAM details.
push af
; first, get the two LSB at MSB pos.
rrca \ rrca
push af ; --> lvl 1
and 0b11000000
; That's our first address byte
out (VDP_CTLPORT), a
pop af ; <-- lvl 1
; Then, get those 3 other bits at LSB pos. Our popped A has already
; done 2 RRCA, which means that everything is in place.
and 0b00000111
or 0x78
out (VDP_CTLPORT), a
; We're at the right place. Let's just spit 32*2 null bytes
xor a
push bc ; --> lvl 1
ld b, 64
.loop:
out (VDP_DATAPORT), a
djnz .loop
pop bc ; <-- lvl 1
pop af
ret
; Convert ASCII char in A into a tile index corresponding to that character.
; When a character is unknown, returns 0x5e (a '~' char).
vdpConv:
; The font is organized to closely match ASCII, so this is rather easy.
; We simply subtract 0x20 from incoming A
sub 0x20
cp 0x5f
ret c ; A < 0x5f, good
ld a, 0x5e
ret
; VDP initialisation data
vdpInitData:
; 0x8x == set register X

View File

@ -16,12 +16,16 @@
.equ PAD_RAMSTART RAMSTART
.inc "sms/pad.asm"
.equ VDP_RAMSTART PAD_RAMEND
.inc "sms/vdp.asm"
.equ GRID_RAMSTART PAD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.inc "grid.asm"
.equ STDIO_RAMSTART VDP_RAMEND
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC padGetC
.equ STDIO_PUTC vdpPutC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
; *** BASIC ***
@ -51,6 +55,7 @@ init:
ld sp, RAMEND
call gridInit
call padInit
call vdpInit
call basInit

View File

@ -18,12 +18,16 @@
.equ KBD_FETCHKC smskbdFetchKCB
.inc "kbd.asm"
.equ VDP_RAMSTART KBD_RAMEND
.inc "sms/vdp.asm"
.equ GRID_RAMSTART KBD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.inc "grid.asm"
.equ STDIO_RAMSTART VDP_RAMEND
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC kbdGetC
.equ STDIO_PUTC vdpPutC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
; *** BASIC ***
@ -64,6 +68,7 @@ init:
out (0x3f), a
call kbdInit
call gridInit
call vdpInit
call basInit
jp basStart

View File

@ -47,12 +47,16 @@
.equ KBD_FETCHKC smskbdFetchKCB
.inc "kbd.asm"
.equ VDP_RAMSTART KBD_RAMEND
.inc "sms/vdp.asm"
.equ GRID_RAMSTART KBD_RAMEND
.equ GRID_COLS VDP_COLS
.equ GRID_ROWS VDP_ROWS
.equ GRID_SETCELL vdpSetCell
.inc "grid.asm"
.equ STDIO_RAMSTART VDP_RAMEND
.equ STDIO_RAMSTART GRID_RAMEND
.equ STDIO_GETC kbdGetC
.equ STDIO_PUTC vdpPutC
.equ STDIO_PUTC gridPutC
.inc "stdio.asm"
.equ MMAP_START 0xd700
@ -124,6 +128,7 @@ init:
call fsOn
call kbdInit
call gridInit
call vdpInit
call basInit
@ -165,7 +170,7 @@ f1PutB:
ld ix, FS_HANDLES+FS_HANDLE_SIZE
jp fsPutB
; last time I checked, PC at this point was 0x1e92. Let's give us a nice margin
; last time I checked, PC at this point was 0x128f. Let's give us a nice margin
; for the start of ed.
.fill 0x1f00-$
.bin "ed.bin"