//! The text console, drawn into the virtio-gpu framebuffer. //! //! VGA text mode gave us character cells for free: write a byte, the video //! card draws the letter. A framebuffer is just pixels, so we keep the same //! grid of (character, colour) cells ourselves and *render* each cell from //! the bitmap font (font.rs, 8x16 pixels per glyph). 1024x768 pixels / //! 8x16 = 128 columns x 48 rows. //! //! Scrolling moves the pixels up one text row (a big memmove) instead of //! re-rendering every glyph. After a print we tell the GPU which pixel rows //! changed (`flush`) -- only those get copied to the host. //! //! The console can be confined to a *viewport* (a rectangle of the screen), //! which the `tile` demo uses to put the shell in one tile of its layout. //! vga_buffer.rs decides whether text goes here or to the VGA buffer. use crate::font::{FONT, GLYPH_HEIGHT, GLYPH_WIDTH}; use crate::virtio::gpu::{self, FbInfo}; use alloc::{vec, vec::Vec}; /// The 16 VGA colours as 0x00RRGGBB, so colour attributes mean the same /// on both consoles. pub const PALETTE: [u32; 16] = [ 0x000000, 0x0000aa, 0x00aa00, 0x00aaaa, 0xaa0000, 0xaa00aa, 0xaa5500, 0xaaaaaa, 0x555555, 0x5555ff, 0x55ff55, 0x55ffff, 0xff5555, 0xff55ff, 0xffff55, 0xffffff, ]; /// A rectangle in pixels. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rect { pub x: u32, pub y: u32, pub w: u32, pub h: u32, } pub struct FbConsole { fb: FbInfo, view: Rect, cols: usize, rows: usize, cells: Vec<(u8, u8)>, // (glyph, colour attribute), row by row dirty: Option<(u32, u32)>, // pixel rows [first, last) that changed cursor: Option<(usize, usize)>, } impl FbConsole { pub fn new(fb: FbInfo) -> FbConsole { let view = Rect { x: 0, y: 0, w: fb.width, h: fb.height }; let mut con = FbConsole { fb, view, cols: 0, rows: 0, cells: Vec::new(), dirty: None, cursor: None }; con.set_view(view, 0x0a); con } pub fn cols(&self) -> usize { self.cols } pub fn rows(&self) -> usize { self.rows } pub fn view(&self) -> Rect { self.view } /// Move the console to `view` (clipped to the screen; at least 16x4 /// cells). Keeps the bottom-most text, clears the new area with /// background attribute `attr`. pub fn set_view(&mut self, view: Rect, attr: u8) { let x = view.x.min(self.fb.width - 16 * GLYPH_WIDTH as u32); let y = view.y.min(self.fb.height - 4 * GLYPH_HEIGHT as u32); let w = view.w.clamp(16 * GLYPH_WIDTH as u32, self.fb.width - x); let h = view.h.clamp(4 * GLYPH_HEIGHT as u32, self.fb.height - y); let (cols, rows) = (w as usize / GLYPH_WIDTH, h as usize / GLYPH_HEIGHT); let mut cells = vec![(b' ', attr); cols * rows]; // Copy the old text, bottom rows to bottom rows. for r in 0..rows.min(self.rows) { let (old_r, new_r) = (self.rows - 1 - r, rows - 1 - r); for c in 0..cols.min(self.cols) { cells[new_r * cols + c] = self.cells[old_r * self.cols + c]; } } self.view = Rect { x, y, w, h }; (self.cols, self.rows, self.cells, self.cursor) = (cols, rows, cells, None); self.fill_px(self.view, PALETTE[(attr >> 4) as usize & 15]); for r in 0..rows { for c in 0..cols { self.draw_cell(r, c); } } self.mark_dirty(0, h); } fn fill_px(&mut self, r: Rect, color: u32) { for y in r.y..r.y + r.h { let row = unsafe { self.pixel_row(y) }; row[r.x as usize..(r.x + r.w) as usize].fill(color); } } /// Pixel row `y` of the framebuffer. unsafe fn pixel_row(&self, y: u32) -> &'static mut [u32] { let start = unsafe { self.fb.pixels().add((y * self.fb.stride) as usize) }; unsafe { core::slice::from_raw_parts_mut(start, self.fb.width as usize) } } /// Mark pixel rows [y0, y1) of the view as changed. fn mark_dirty(&mut self, y0: u32, y1: u32) { let (y0, y1) = (self.view.y + y0, self.view.y + y1); self.dirty = Some(match self.dirty { Some((a, b)) => (a.min(y0), b.max(y1)), None => (y0, y1), }); } /// Render one cell from the font: a set bit is foreground colour, a /// clear one background. The cursor is an underline in the last 2 rows. fn draw_cell(&mut self, row: usize, col: usize) { let (glyph, attr) = self.cells[row * self.cols + col]; let fg = PALETTE[attr as usize & 15]; let bg = PALETTE[(attr >> 4) as usize & 15]; let x0 = self.view.x as usize + col * GLYPH_WIDTH; let y0 = self.view.y + (row * GLYPH_HEIGHT) as u32; let cursor_here = self.cursor == Some((row, col)); for (dy, &bits) in FONT[glyph as usize].iter().enumerate() { let line = unsafe { self.pixel_row(y0 + dy as u32) }; let bits = if cursor_here && dy >= GLYPH_HEIGHT - 2 { 0xff } else { bits }; for dx in 0..GLYPH_WIDTH { line[x0 + dx] = if bits & (0x80 >> dx) != 0 { fg } else { bg }; } } let y = (row * GLYPH_HEIGHT) as u32; self.mark_dirty(y, y + GLYPH_HEIGHT as u32); } pub fn put(&mut self, row: usize, col: usize, glyph: u8, attr: u8) { if row < self.rows && col < self.cols { self.cells[row * self.cols + col] = (glyph, attr); self.draw_cell(row, col); } } pub fn cell(&self, row: usize, col: usize) -> (u8, u8) { self.cells[row * self.cols + col] } /// Show the cursor at (row, col) (None: hide it). pub fn set_cursor(&mut self, at: Option<(usize, usize)>) { let at = at.filter(|&(r, c)| r < self.rows && c < self.cols); if at == self.cursor { return; } let old = core::mem::replace(&mut self.cursor, at); for (r, c) in [old, at].into_iter().flatten() { self.draw_cell(r, c); } } /// Everything up one text row; the bottom row becomes blank (`attr`). pub fn scroll_up(&mut self, attr: u8) { self.set_cursor(None); // or its underline would move up too self.cells.copy_within(self.cols.., 0); let last = (self.rows - 1) * self.cols; self.cells[last..].fill((b' ', attr)); // Move the pixels: row y+16 -> row y, for the whole text area. let (x0, x1) = (self.view.x as usize, self.view.x as usize + self.cols * GLYPH_WIDTH); let text_h = (self.rows * GLYPH_HEIGHT) as u32; let stride = self.fb.stride as usize; if x0 == 0 && x1 == stride { // Full-width view: one big contiguous move. let base = self.view.y as usize * stride; let len = text_h as usize * stride; let all = unsafe { core::slice::from_raw_parts_mut(self.fb.pixels().add(base), len) }; all.copy_within(GLYPH_HEIGHT * stride.., 0); } else { for y in self.view.y..self.view.y + text_h - GLYPH_HEIGHT as u32 { let src = unsafe { self.pixel_row(y + GLYPH_HEIGHT as u32) }; let dst = unsafe { self.pixel_row(y) }; dst[x0..x1].copy_from_slice(&src[x0..x1]); } } for c in 0..self.cols { self.draw_cell(self.rows - 1, c); } self.mark_dirty(0, text_h); } /// Blank every cell (rows `from..`). pub fn clear_rows(&mut self, from: usize, attr: u8) { for r in from..self.rows { for c in 0..self.cols { self.put(r, c, b' ', attr); } } } /// Send the changed pixel rows to the screen. pub fn flush(&mut self) { if let Some((y0, y1)) = self.dirty.take() { gpu::flush(self.view.x, y0, self.view.w, y1 - y0); } } }