#![no_std]
#![cfg_attr(feature = "const_fn", feature(const_fn))]
extern crate console_traits;
#[macro_use]
extern crate const_ft;
mod charset;
pub mod freebsd_cp850;
pub mod freebsd_teletext;
mod maps;
pub use charset::*;
pub use console_traits::*;
use core::sync::atomic::{AtomicUsize, Ordering};
use maps::RGB_MAPS;
pub const MODE0_USABLE_LINES: usize = 576;
pub const MODE0_USABLE_COLS: usize = 384;
pub const MODE0_HORIZONTAL_OCTETS: usize = 50;
pub const MODE0_USABLE_HORIZONTAL_OCTETS: usize = 48;
pub const MODE0_TEXT_NUM_COLS: usize = MODE0_USABLE_COLS / MAX_FONT_WIDTH;
pub const MODE0_TEXT_MAX_COL: usize = MODE0_TEXT_NUM_COLS - 1;
pub const MODE0_TEXT_NUM_ROWS: usize = MODE0_USABLE_LINES / MAX_FONT_HEIGHT;
pub const MODE0_TEXT_MAX_ROW: usize = MODE0_TEXT_NUM_ROWS - 1;
pub const MODE2_WIDTH_PIXELS: usize = 384;
pub const MODE2_USABLE_LINES: usize = 288;
const H_VISIBLE_AREA_20MHZ: u32 = 400;
const H_FRONT_PORCH_20MHZ: u32 = 20;
const H_SYNC_PULSE_20MHZ: u32 = 64;
const H_BACK_PORCH_20MHZ: u32 = 44;
const H_WHOLE_LINE_20MHZ: u32 =
H_VISIBLE_AREA_20MHZ + H_FRONT_PORCH_20MHZ + H_SYNC_PULSE_20MHZ + H_BACK_PORCH_20MHZ;
const V_VISIBLE_AREA: usize = 600;
const V_FRONT_PORCH: usize = 1;
const V_SYNC_PULSE: usize = 4;
const V_BACK_PORCH: usize = 23;
const V_WHOLE_FRAME: usize = V_SYNC_PULSE + V_BACK_PORCH + V_VISIBLE_AREA + V_FRONT_PORCH;
const V_TOP_BORDER: usize = 12;
const V_BOTTOM_BORDER: usize = 12;
const MAX_FONT_HEIGHT: usize = 16;
const MAX_FONT_WIDTH: usize = 8;
const V_SYNC_PULSE_FIRST: usize = 0;
const V_BACK_PORCH_FIRST: usize = V_SYNC_PULSE_FIRST + V_SYNC_PULSE;
const V_TOP_BORDER_FIRST: usize = V_BACK_PORCH_FIRST + V_BACK_PORCH;
const V_TOP_BORDER_LAST: usize = V_DATA_FIRST - 1;
const V_DATA_FIRST: usize = V_TOP_BORDER_FIRST + V_TOP_BORDER;
const V_DATA_LAST: usize = V_BOTTOM_BORDER_FIRST - 1;
const V_BOTTOM_BORDER_FIRST: usize = V_DATA_FIRST + (MAX_FONT_HEIGHT * MODE0_TEXT_NUM_ROWS);
const V_BOTTOM_BORDER_LAST: usize = V_FRONT_PORCH_FIRST - 1;
const V_FRONT_PORCH_FIRST: usize = V_BOTTOM_BORDER_FIRST + V_BOTTOM_BORDER;
const DEFAULT_ATTR: Attr = Attr::new(Colour::White, Colour::Blue);
const CURSOR: Char = Char::LowLine;
pub trait Hardware {
fn configure(&mut self, mode_info: &ModeInfo);
fn vsync_on(&mut self);
fn vsync_off(&mut self);
fn write_pixels(&mut self, xrgb: XRGBColour);
}
trait VideoMode {
fn octets(&self) -> usize;
fn clock_speed(&self) -> u32;
}
#[derive(Debug)]
pub struct ModeInfo {
pub width: u32,
pub visible_width: u32,
pub sync_end: u32,
pub line_start: u32,
pub clock_rate: u32,
pub num_lines: u32,
pub visible_lines: u32,
}
#[repr(C)]
pub struct FrameBuffer<T>
where
T: Hardware,
{
line_no: AtomicUsize,
frame: usize,
text_buffer: [Mode0TextRow; MODE0_TEXT_NUM_ROWS + 1],
roller_buffer: [u16; MODE0_USABLE_LINES],
hw: Option<T>,
attr: Attr,
pos: Position,
mode: ControlCharMode,
escape_mode: EscapeCharMode,
mode2: Option<Mode2>,
font: Option<*const u8>,
cursor_visible: bool,
under_cursor: Char,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Attr(u8);
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Colour {
White = 7,
Yellow = 6,
Magenta = 5,
Red = 4,
Cyan = 3,
Green = 2,
Blue = 1,
Black = 0,
}
#[derive(Debug, Copy, Clone)]
pub struct XRGBColour(pub u32);
pub struct Mode2 {
buffer: *const u8,
start: usize,
end: usize,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Point(pub usize, pub usize);
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum DoubleHeightMode {
Normal,
Top,
Bottom,
}
#[derive(Copy, Clone)]
pub struct Mode0TextRow {
pub double_height: DoubleHeightMode,
pub glyphs: [(Char, Attr); MODE0_TEXT_NUM_COLS],
}
impl<T> FrameBuffer<T>
where
T: Hardware,
{
const_ft! {
pub fn new() -> FrameBuffer<T> {
FrameBuffer {
line_no: AtomicUsize::new(0),
frame: 0,
text_buffer: [Mode0TextRow {
double_height: DoubleHeightMode::Normal,
glyphs: [(Char::Null, DEFAULT_ATTR); MODE0_TEXT_NUM_COLS],
}; MODE0_TEXT_NUM_ROWS + 1],
roller_buffer: [0; MODE0_USABLE_LINES],
hw: None,
pos: Position {
row: Row(0),
col: Col(0),
},
attr: DEFAULT_ATTR,
mode: ControlCharMode::Interpret,
escape_mode: EscapeCharMode::Waiting,
mode2: None,
font: None,
cursor_visible: true,
under_cursor: Char::Space,
}
}
}
pub fn init(&mut self, mut hw: T) {
assert_eq!(MAX_FONT_WIDTH, 8);
let mode_info = ModeInfo {
width: H_WHOLE_LINE_20MHZ,
visible_width: H_VISIBLE_AREA_20MHZ,
sync_end: H_SYNC_PULSE_20MHZ,
line_start: H_SYNC_PULSE_20MHZ + H_BACK_PORCH_20MHZ,
clock_rate: 20_000_000,
num_lines: V_WHOLE_FRAME as u32,
visible_lines: V_VISIBLE_AREA as u32,
};
hw.configure(&mode_info);
self.hw = Some(hw);
for (idx, line) in self.roller_buffer.iter_mut().enumerate() {
*line = idx as u16;
}
self.clear();
}
pub fn borrow_hw_mut(&mut self) -> Option<&mut T> {
if let Some(x) = &mut self.hw {
Some(x)
} else {
None
}
}
pub fn borrow_hw(&self) -> Option<&T> {
if let Some(x) = &self.hw {
Some(x)
} else {
None
}
}
pub fn set_cursor_visible(&mut self, visible: bool) {
if visible != self.cursor_visible {
if visible {
self.cursor_visible = true;
self.under_cursor = self.current_cell().0;
self.current_cell().0 = CURSOR;
} else {
self.cursor_visible = false;
self.current_cell().0 = self.under_cursor;
}
}
}
pub fn mode2(&mut self, buffer: &[u8], start_line: usize) {
let length = buffer.len();
let buffer_lines = length / MODE0_USABLE_HORIZONTAL_OCTETS;
let mode2 = Mode2 {
buffer: buffer.as_ptr(),
start: start_line,
end: start_line + (2 * buffer_lines),
};
self.mode2 = Some(mode2);
}
pub fn mode2_shift(&mut self, new_start_line: usize) {
if let Some(mode2) = self.mode2.as_mut() {
mode2.start = new_start_line;
}
}
pub fn mode2_release(&mut self) {
let mut mode2_opt = None;
core::mem::swap(&mut self.mode2, &mut mode2_opt);
}
pub fn map_line(&mut self, visible_line: u16, rendered_line: u16) {
if (rendered_line as usize) < MODE0_USABLE_LINES {
if let Some(n) = self.roller_buffer.get_mut(visible_line as usize) {
*n = rendered_line;
}
}
}
pub fn frame(&self) -> usize {
self.frame
}
pub fn line(&self) -> Option<usize> {
let line = self.line_no.load(Ordering::Relaxed);
if line >= V_DATA_FIRST && line <= V_DATA_LAST {
Some(line - V_DATA_FIRST)
} else {
None
}
}
pub fn total_line(&self) -> u64 {
let line_a = self.line_no.load(Ordering::Relaxed);
let mut f = self.frame;
let line_b = self.line_no.load(Ordering::Relaxed);
if line_b < line_a {
f = self.frame;
}
((f as u64) * (V_WHOLE_FRAME as u64)) + (line_b as u64)
}
pub fn isr_sol(&mut self) {
match self.line_no.load(Ordering::Relaxed) {
V_BOTTOM_BORDER_FIRST..=V_BOTTOM_BORDER_LAST => {
self.solid_line();
}
V_TOP_BORDER_FIRST..=V_TOP_BORDER_LAST => {
self.solid_line();
}
V_DATA_FIRST..=V_DATA_LAST => {
self.calculate_pixels();
}
V_BACK_PORCH_FIRST => {
if let Some(ref mut hw) = self.hw {
hw.vsync_off();
}
}
V_FRONT_PORCH_FIRST => {
self.frame = self.frame.wrapping_add(1);
}
V_WHOLE_FRAME => {
self.line_no.store(0, Ordering::Relaxed);
if let Some(ref mut hw) = self.hw {
hw.vsync_on();
}
}
_ => {
}
}
self.line_no.fetch_add(1, Ordering::Relaxed);
}
fn solid_line(&mut self) {
if let Some(ref mut hw) = self.hw {
for _ in 0..MODE0_HORIZONTAL_OCTETS {
hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
}
}
}
fn calculate_pixels(&mut self) {
let real_line = self.line_no.load(Ordering::Relaxed) - V_DATA_FIRST;
let line = self.roller_buffer[real_line as usize] as usize;
let text_row = line / MAX_FONT_HEIGHT;
let row = &self.text_buffer[text_row];
let font_row = match row.double_height {
DoubleHeightMode::Normal => line % MAX_FONT_HEIGHT,
DoubleHeightMode::Top => (line % MAX_FONT_HEIGHT) / 2,
DoubleHeightMode::Bottom => ((line % MAX_FONT_HEIGHT) + MAX_FONT_HEIGHT) / 2,
};
let font_table = self
.font
.unwrap_or_else(|| freebsd_cp850::FONT_DATA.as_ptr());
if let Some(ref mut hw) = self.hw {
hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
let mut need_text = true;
if let Some(mode2) = self.mode2.as_ref() {
if line >= mode2.start && line < mode2.end && text_row < MODE0_TEXT_NUM_ROWS {
let framebuffer_line = (line - mode2.start) >> 1;
let start = framebuffer_line * MODE0_USABLE_HORIZONTAL_OCTETS;
let framebuffer_offsets = (start as isize)
..(start as isize + MODE0_USABLE_HORIZONTAL_OCTETS as isize);
for ((_, attr), framebuffer_offset) in self.text_buffer[text_row]
.glyphs
.iter()
.zip(framebuffer_offsets)
{
let w = unsafe { *mode2.buffer.offset(framebuffer_offset) };
let rgb_addr = unsafe {
RGB_MAPS
.as_ptr()
.offset(((attr.0 as isize) * 256_isize) + (w as isize))
};
let rgb_word = unsafe { *rgb_addr };
hw.write_pixels(rgb_word);
}
need_text = false;
}
}
if need_text {
let font_table = unsafe { font_table.add(font_row) };
for (ch, attr) in row.glyphs.iter() {
let index = (*ch as isize) * (MAX_FONT_HEIGHT as isize);
let mono_pixels = unsafe { *font_table.offset(index) };
let rgb_addr = unsafe {
RGB_MAPS
.as_ptr()
.offset(((attr.0 as isize) * 256_isize) + (mono_pixels as isize))
};
let rgb_word = unsafe { *rgb_addr };
hw.write_pixels(rgb_word);
}
}
hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
}
}
pub fn set_custom_font(&mut self, new_font: Option<&'static [u8]>) {
self.font = match new_font {
Some(x) => {
assert_eq!(x.len(), 256 * MAX_FONT_HEIGHT);
Some(x.as_ptr())
}
None => None,
};
}
pub fn clear(&mut self) {
for row in self.text_buffer.iter_mut() {
for slot in row.glyphs.iter_mut() {
*slot = (Char::Space, self.attr);
}
row.double_height = DoubleHeightMode::Normal;
}
self.pos = Position::origin();
}
pub fn write_glyph_at(&mut self, glyph: Char, pos: Position, attr: Option<Attr>) {
if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
self.under_cursor = glyph;
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 =
attr.unwrap_or(self.attr);
} else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
(glyph, attr.unwrap_or(self.attr));
}
}
pub fn read_glyph_at(&mut self, pos: Position) -> Option<(Char, Attr)> {
if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
Some((
self.under_cursor,
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1,
))
} else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
Some(self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize])
} else {
None
}
}
pub fn write_glyph(&mut self, glyph: Char, attr: Option<Attr>) {
if self.cursor_visible {
self.under_cursor = glyph;
self.current_cell().1 = attr.unwrap_or(self.attr);
} else {
*self.current_cell() = (glyph, attr.unwrap_or(self.attr));
}
self.move_cursor_right().unwrap();
}
pub fn write_char(&mut self, ch: u8, attr: Option<Attr>) {
self.write_glyph(Char::from_byte(ch), attr);
}
pub fn set_attr_at(&mut self, pos: Position, attr: Attr) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 = attr;
}
pub fn set_line_mode_at(&mut self, row: Row, double_height: DoubleHeightMode) {
self.text_buffer[row.0 as usize].double_height = double_height;
}
pub fn set_line_mode(&mut self, double_height: DoubleHeightMode) {
self.text_buffer[self.pos.row.0 as usize].double_height = double_height;
}
pub fn set_attr(&mut self, attr: Attr) -> Attr {
let old = self.attr;
self.attr = attr;
old
}
pub fn get_attr(&mut self) -> Attr {
self.attr
}
fn current_cell(&mut self) -> &mut (Char, Attr) {
&mut self.text_buffer[self.pos.row.0 as usize].glyphs[self.pos.col.0 as usize]
}
}
impl<T> BaseConsole for FrameBuffer<T>
where
T: Hardware,
{
type Error = ();
fn get_width(&self) -> Col {
Col(MODE0_TEXT_MAX_COL as u8)
}
fn get_height(&self) -> Row {
Row(MODE0_TEXT_MAX_ROW as u8)
}
fn set_col(&mut self, col: Col) -> Result<(), Self::Error> {
if col <= self.get_width() {
if self.cursor_visible {
self.current_cell().0 = self.under_cursor;
self.pos.col = col;
self.under_cursor = self.current_cell().0;
self.current_cell().0 = CURSOR;
} else {
self.pos.col = col;
}
Ok(())
} else {
Err(())
}
}
fn set_row(&mut self, row: Row) -> Result<(), Self::Error> {
if row <= self.get_height() {
if self.cursor_visible {
self.current_cell().0 = self.under_cursor;
self.pos.row = row;
self.under_cursor = self.current_cell().0;
self.current_cell().0 = CURSOR;
} else {
self.pos.row = row;
}
Ok(())
} else {
Err(())
}
}
fn set_pos(&mut self, pos: Position) -> Result<(), Self::Error> {
if pos.row <= self.get_height() && pos.col <= self.get_width() {
if self.cursor_visible {
self.current_cell().0 = self.under_cursor;
self.pos = pos;
self.under_cursor = self.current_cell().0;
self.current_cell().0 = CURSOR;
} else {
self.pos = pos;
}
Ok(())
} else {
Err(())
}
}
fn get_pos(&self) -> Position {
self.pos
}
fn set_control_char_mode(&mut self, mode: ControlCharMode) {
self.mode = mode;
}
fn get_control_char_mode(&self) -> ControlCharMode {
self.mode
}
fn set_escape_char_mode(&mut self, mode: EscapeCharMode) {
self.escape_mode = mode;
}
fn get_escape_char_mode(&self) -> EscapeCharMode {
self.escape_mode
}
fn scroll_screen(&mut self) -> Result<(), Self::Error> {
let old_cursor = self.cursor_visible;
self.set_cursor_visible(false);
for line in 0..MODE0_TEXT_NUM_ROWS - 1 {
self.text_buffer[line] = self.text_buffer[line + 1];
}
for slot in self.text_buffer[MODE0_TEXT_MAX_ROW].glyphs.iter_mut() {
*slot = (Char::Space, self.attr);
}
self.set_cursor_visible(old_cursor);
Ok(())
}
}
impl<T> AsciiConsole for FrameBuffer<T>
where
T: Hardware,
{
fn handle_escape(&mut self, escaped_char: u8) -> bool {
match escaped_char {
b'W' => {
self.attr.set_fg(Colour::White);
}
b'Y' => {
self.attr.set_fg(Colour::Yellow);
}
b'M' => {
self.attr.set_fg(Colour::Magenta);
}
b'R' => {
self.attr.set_fg(Colour::Red);
}
b'C' => {
self.attr.set_fg(Colour::Cyan);
}
b'G' => {
self.attr.set_fg(Colour::Green);
}
b'B' => {
self.attr.set_fg(Colour::Blue);
}
b'K' => {
self.attr.set_fg(Colour::Black);
}
b'w' => {
self.attr.set_bg(Colour::White);
}
b'y' => {
self.attr.set_bg(Colour::Yellow);
}
b'm' => {
self.attr.set_bg(Colour::Magenta);
}
b'r' => {
self.attr.set_bg(Colour::Red);
}
b'c' => {
self.attr.set_bg(Colour::Cyan);
}
b'g' => {
self.attr.set_bg(Colour::Green);
}
b'b' => {
self.attr.set_bg(Colour::Blue);
}
b'k' => {
self.attr.set_bg(Colour::Black);
}
b'^' => {
self.set_line_mode(DoubleHeightMode::Top);
}
b'v' => {
self.set_line_mode(DoubleHeightMode::Bottom);
}
b'-' => {
self.set_line_mode(DoubleHeightMode::Normal);
}
b'Z' => {
self.clear();
}
_ => {}
}
true
}
fn write_char_at(&mut self, ch: u8, pos: Position) -> Result<(), Self::Error> {
if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
self.under_cursor = Char::from_byte(ch);
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 = self.attr;
} else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
(Char::from_byte(ch), self.attr);
}
Ok(())
}
}
impl<T> core::fmt::Write for FrameBuffer<T>
where
T: Hardware,
{
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for ch in s.chars() {
self.write_character(Char::map_char(ch) as u8)
.map_err(|_| core::fmt::Error)?;
}
Ok(())
}
}
impl Attr {
const FG_BITS: u8 = 0b0011_1000;
const BG_BITS: u8 = 0b0000_0111;
pub const fn new(fg: Colour, bg: Colour) -> Attr {
Attr(((fg as u8) << 3) + (bg as u8))
}
pub fn set_fg(&mut self, fg: Colour) -> &mut Attr {
self.0 = ((fg as u8) << 3) + (self.0 & !Self::FG_BITS);
self
}
pub fn set_bg(&mut self, bg: Colour) -> &mut Attr {
self.0 = (self.0 & !Self::BG_BITS) + (bg as u8);
self
}
pub fn as_u8(self) -> u8 {
self.0
}
}
impl core::default::Default for Attr {
fn default() -> Self {
DEFAULT_ATTR
}
}
impl Colour {
pub fn into_pixels(self) -> XRGBColour {
match self {
Colour::White => XRGBColour::new(0xFF, 0xFF, 0xFF),
Colour::Yellow => XRGBColour::new(0xFF, 0xFF, 0x00),
Colour::Magenta => XRGBColour::new(0xFF, 0x00, 0xFF),
Colour::Red => XRGBColour::new(0xFF, 0x00, 0x00),
Colour::Cyan => XRGBColour::new(0x00, 0xFF, 0xFF),
Colour::Green => XRGBColour::new(0x00, 0xFF, 0x00),
Colour::Blue => XRGBColour::new(0x00, 0x00, 0xFF),
Colour::Black => XRGBColour::new(0x00, 0x00, 0x00),
}
}
}
impl XRGBColour {
pub const fn new(red: u8, green: u8, blue: u8) -> XRGBColour {
XRGBColour(((red as u32) << 16) | ((green as u32) << 8) | (blue as u32))
}
pub const fn red(self) -> u32 {
(self.0 >> 16) & 0xFF
}
pub const fn green(self) -> u32 {
(self.0 >> 8) & 0xFF
}
pub const fn blue(self) -> u32 {
self.0 & 0xFF
}
pub const fn pixel_has_red(self, pixel: u8) -> bool {
((self.0 >> (16 + (7 - pixel))) & 1) == 1
}
pub const fn pixel_has_green(self, pixel: u8) -> bool {
((self.0 >> (8 + (7 - pixel))) & 1) == 1
}
pub const fn pixel_has_blue(self, pixel: u8) -> bool {
((self.0 >> (7 - pixel)) & 1) == 1
}
}