131 lines
3.2 KiB
Nim
131 lines
3.2 KiB
Nim
# Phase 27 Part 2: The CRT Scanline Renderer
|
|
import term_font
|
|
import ion_client
|
|
|
|
const TERM_COLS* = 100
|
|
const TERM_ROWS* = 37
|
|
|
|
# Sovereign Palette (Phased Aesthetics)
|
|
const
|
|
COLOR_SOVEREIGN_BLUE = 0xFF401010'u32
|
|
COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'u32
|
|
COLOR_SCANLINE_DIM = 0xFF300808'u32
|
|
|
|
var grid: array[TERM_ROWS, array[TERM_COLS, char]]
|
|
var cursor_x, cursor_y: int
|
|
var color_fg: uint32 = COLOR_PHOSPHOR_AMBER
|
|
var color_bg: uint32 = COLOR_SOVEREIGN_BLUE
|
|
var fb_ptr: ptr UncheckedArray[uint32]
|
|
var fb_w, fb_h, fb_stride: int
|
|
var ansi_state: int = 0
|
|
|
|
proc term_init*() =
|
|
let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
|
|
fb_ptr = cast[ptr UncheckedArray[uint32]](sys.fb_addr)
|
|
fb_w = int(sys.fb_width)
|
|
fb_h = int(sys.fb_height)
|
|
fb_stride = int(sys.fb_stride)
|
|
cursor_x = 0
|
|
cursor_y = 0
|
|
ansi_state = 0
|
|
|
|
# Initialize Grid
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
grid[row][col] = ' '
|
|
|
|
# Force initial color compliance
|
|
color_fg = COLOR_PHOSPHOR_AMBER
|
|
color_bg = COLOR_SOVEREIGN_BLUE
|
|
|
|
proc term_clear*() =
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
grid[row][col] = ' '
|
|
cursor_x = 0
|
|
cursor_y = 0
|
|
ansi_state = 0
|
|
|
|
proc term_scroll() =
|
|
for row in 0..<(TERM_ROWS-1):
|
|
grid[row] = grid[row + 1]
|
|
for col in 0..<TERM_COLS:
|
|
grid[TERM_ROWS-1][col] = ' '
|
|
|
|
proc term_putc*(ch: char) =
|
|
# ANSI Stripper
|
|
if ansi_state == 1:
|
|
if ch == '[': ansi_state = 2
|
|
else: ansi_state = 0
|
|
return
|
|
if ansi_state == 2:
|
|
if ch >= '@' and ch <= '~': ansi_state = 0
|
|
return
|
|
if ch == '\x1b':
|
|
ansi_state = 1
|
|
return
|
|
|
|
if ch == '\n':
|
|
cursor_x = 0
|
|
cursor_y += 1
|
|
elif ch == '\r':
|
|
cursor_x = 0
|
|
elif ch >= ' ':
|
|
if cursor_x >= TERM_COLS:
|
|
cursor_x = 0
|
|
cursor_y += 1
|
|
if cursor_y >= TERM_ROWS:
|
|
term_scroll()
|
|
cursor_y = TERM_ROWS - 1
|
|
grid[cursor_y][cursor_x] = ch
|
|
cursor_x += 1
|
|
|
|
# --- THE GHOST RENDERER ---
|
|
proc draw_char(cx, cy: int, c: char, fg: uint32, bg: uint32) =
|
|
if fb_ptr == nil: return
|
|
|
|
# Safe Font Mapping
|
|
var glyph_idx = int(c) - 32
|
|
if glyph_idx < 0 or glyph_idx >= 96: glyph_idx = 0 # Space default
|
|
|
|
let px_start = cx * 8
|
|
let py_start = cy * 16
|
|
|
|
for y in 0..15:
|
|
# Scanline Logic: Every 4th line
|
|
let is_scanline = (y mod 4) == 3
|
|
|
|
let row_bits = FONT_BITMAP[glyph_idx][y]
|
|
let screen_y = py_start + y
|
|
if screen_y >= fb_h: break
|
|
|
|
# Optimize inner loop knowing stride is in bytes but using uint32 accessor
|
|
# fb_ptr index is per uint32.
|
|
let row_offset = screen_y * (fb_stride div 4)
|
|
|
|
for x in 0..7:
|
|
let screen_x = px_start + x
|
|
if screen_x >= fb_w: break
|
|
|
|
let pixel_idx = row_offset + screen_x
|
|
|
|
# Bit Check: MSB first (0x80 >> x)
|
|
let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0
|
|
|
|
if is_pixel:
|
|
if is_scanline:
|
|
fb_ptr[pixel_idx] = fg and 0xFFE0E0E0'u32
|
|
else:
|
|
fb_ptr[pixel_idx] = fg
|
|
else:
|
|
if is_scanline:
|
|
fb_ptr[pixel_idx] = COLOR_SCANLINE_DIM
|
|
else:
|
|
fb_ptr[pixel_idx] = bg
|
|
|
|
proc term_render*() =
|
|
if fb_ptr == nil: return
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
draw_char(col, row, grid[row][col], color_fg, color_bg)
|