diff options
Diffstat (limited to 'src/devices/screen.rs')
-rw-r--r-- | src/devices/screen.rs | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/src/devices/screen.rs b/src/devices/screen.rs new file mode 100644 index 0000000..df9539a --- /dev/null +++ b/src/devices/screen.rs @@ -0,0 +1,469 @@ +mod sprite_data; +mod vector_points; + +pub use sprite_data::*; +pub use vector_points::*; + +use geometry::HasDimensions; +use phosphor::*; + +use std::cmp::{min, max, Ordering}; +use std::iter::zip; + +pub type ScreenDimensions = geometry::Dimensions<u16>; +pub type ScreenPosition = geometry::Point<u16>; + +#[derive(Copy, Clone)] +pub enum ScreenLayer { Background, Foreground } + +pub struct ScreenDevice { + pub wake_flag: bool, + /// Each byte represents a screen pixel, left-right-top-bottom. + // Only the bottom four bits of each byte are used. + pub foreground: Vec<u8>, + pub background: Vec<u8>, + pub is_dirty: bool, + pub is_resizable: bool, + + pub cursor: ScreenPosition, + pub dimensions: ScreenDimensions, + pub sprite_data: SpriteData, + pub palette: [Colour; 16], + pub palette_high: u8, + pub sprite_colours: [u8; 4], + pub vector: VectorPoints, +} + +impl ScreenDevice { + pub fn new() -> Self { + Self { + wake_flag: false, + + foreground: Vec::new(), + background: Vec::new(), + is_dirty: false, + is_resizable: true, + + cursor: ScreenPosition::ZERO, + dimensions: ScreenDimensions::ZERO, + sprite_data: SpriteData::new(), + palette: [Colour::BLACK; 16], + palette_high: 0, + sprite_colours: [0; 4], + + vector: VectorPoints::new(), + } + } + + pub fn set_size(&mut self, dimensions: ScreenDimensions) { + self.is_resizable = false; + self.resize(dimensions); + } + + pub fn resize(&mut self, dimensions: ScreenDimensions) { + let old_width = self.dimensions.width as usize; + let old_height = self.dimensions.height as usize; + let new_width = dimensions.width as usize; + let new_height = dimensions.height as usize; + let new_area = dimensions.area_usize(); + let y_range = 0..min(old_height, new_height); + + match new_width.cmp(&old_width) { + Ordering::Less => { + for y in y_range { + let from = y * old_width; + let to = y * new_width; + let len = new_width; + self.foreground.copy_within(from..from+len, to); + self.background.copy_within(from..from+len, to); + } + self.foreground.resize(new_area, 0); + self.background.resize(new_area, 0); + }, + Ordering::Greater => { + self.foreground.resize(new_area, 0); + self.background.resize(new_area, 0); + for y in y_range.rev() { + let from = y * old_width; + let to = y * new_width; + let len = old_width; + self.foreground.copy_within(from..from+len, to); + self.background.copy_within(from..from+len, to); + self.foreground[to+len..to+new_width].fill(0); + self.background[to+len..to+new_width].fill(0); + } + }, + Ordering::Equal => { + self.foreground.resize(new_area, 0); + self.background.resize(new_area, 0); + }, + }; + + self.dimensions = dimensions; + self.is_dirty = true; + self.wake_flag = true; + } + + pub fn render(&mut self, buffer: &mut Buffer) { + // Pre-calculate a lookup table for the colour palette + let mut palette = [Colour::BLACK; 256]; + for (i, c) in palette.iter_mut().enumerate() { + match i > 0x0f { + true => *c = self.palette[i >> 4], + false => *c = self.palette[i & 0x0f], + } + }; + let b_width = buffer.width() as usize; + let b_height = buffer.height() as usize; + let s_width = self.dimensions.width() as usize; + let s_height = self.dimensions.height() as usize; + + // Write colours to the buffer + if b_width == s_width && b_height == s_height { + let screen_iter = zip(&self.background, &self.foreground); + let buffer_iter = buffer.as_mut_slice(); + for (b, (bg, fg)) in zip(buffer_iter, screen_iter) { + *b = palette[(fg << 4 | bg) as usize]; + } + } else { + let width = min(b_width, s_width); + let height = min(b_height, s_height); + let width_excess = b_width.saturating_sub(width); + let b_slice = &mut buffer.as_mut_slice(); + let mut bi = 0; + let mut si = 0; + for _ in 0..height { + let b_iter = &mut b_slice[bi..bi+width]; + let s_iter = zip( + &self.background[si..si+width], + &self.foreground[si..si+width], + ); + for (b, (bg, fg)) in zip(b_iter, s_iter) { + *b = palette[(fg << 4 | bg) as usize]; + } + b_slice[bi+width..bi+width+width_excess].fill(palette[0]); + bi += b_width; + si += s_width; + } + b_slice[bi..].fill(palette[0]); + } + + // Set flags + self.is_dirty = false; + } + + pub fn set_palette_high(&mut self, val: u8) { + self.palette_high = val; + } + + pub fn set_palette_low(&mut self, val: u8) { + let index = (self.palette_high >> 4) as usize; + let red = (self.palette_high & 0x0f) * 17; + let green = (val >> 4) * 17; + let blue = (val & 0x0f) * 17; + self.palette[index] = Colour::from_rgb(red, green, blue); + self.is_dirty = true; + } + + pub fn set_sprite_colour_high(&mut self, val: u8) { + self.sprite_colours[0] = val >> 4; + self.sprite_colours[1] = val & 0x0f; + } + + pub fn set_sprite_colour_low(&mut self, val: u8) { + self.sprite_colours[2] = val >> 4; + self.sprite_colours[3] = val & 0x0f; + } + + pub fn shunt(&mut self, val: u8) { + let is_negative = val & 0x80 != 0; + let is_vertical = val & 0x40 != 0; + let dist = (val & 0x3f) as u16; + match (is_negative, is_vertical) { + (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist), + (false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist), + ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist), + ( true, true) => self.cursor.y = self.cursor.y.wrapping_sub(dist), + }; + } + + pub fn draw(&mut self, val: u8) { + self.vector.push(self.cursor); + self.is_dirty = true; + // Parse draw byte + let draw_mode = val & 0x70; + let params = val & 0x0f; + let layer = match val & 0x80 != 0 { + true => ScreenLayer::Foreground, + false => ScreenLayer::Background + }; + match draw_mode { + 0x00 => self.draw_pixel(params, layer, self.cursor), + 0x10 => self.draw_sprite_1bit(params, layer), + 0x20 => self.fill_layer(params, layer), + 0x30 => self.draw_sprite_2bit(params, layer), + 0x40 => self.draw_line(params, layer), + 0x50 => self.draw_rect(params, layer), + 0x60 => todo!("Draw 1-bit sprite triangle"), + 0x70 => self.draw_rect_1bit(params, layer), + _ => unreachable!(), + }; + } + + fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) { + let dim = self.dimensions; + if !dim.contains_point(point) { return } + let index = point.x as usize + ((dim.width as usize) * (point.y as usize)); + match layer { + ScreenLayer::Background => self.background[index] = colour, + ScreenLayer::Foreground => self.foreground[index] = colour, + }; + } + + fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) { + let mut sprite = [0; 64]; + let mut pointer: usize = 0; + let data = self.sprite_data.get_1bit_sprite(); + for row in data { + for x in (0..8).rev() { + sprite[pointer] = (row >> x) & 0x1; + pointer += 1; + } + } + self.draw_sprite(params, layer, sprite); + } + + fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) { + match layer { + ScreenLayer::Background => self.background.fill(colour), + ScreenLayer::Foreground => self.foreground.fill(colour), + } + } + + fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) { + let mut sprite = [0; 64]; + let mut pointer: usize = 0; + let data = self.sprite_data.get_2bit_sprite(); + let (spr1, spr2) = data.split_array_ref::<8>(); + for (row1, row2) in std::iter::zip(spr1, spr2) { + for x in (0..8).rev() { + let bit1 = (row1 >> x << 1) & 0x2; + let bit2 = (row2 >> x) & 0x1; + sprite[pointer] = bit1 | bit2; + pointer += 1; + } + } + self.draw_sprite(params, layer, sprite); + } + + fn draw_line(&mut self, colour: u8, layer: ScreenLayer) { + let points = self.vector.get_pair(); + match (points[0].x == points[1].x, points[0].y == points[1].y) { + (false, false) => self.draw_diagonal_line(colour, layer, points), + (false, true) => self.draw_horizontal_line(colour, layer, points), + ( true, false) => self.draw_vertical_line(colour, layer, points), + ( true, true) => self.draw_pixel(colour, layer, points[0]), + }; + } + + fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + fn abs_diff(v0: u16, v1: u16) -> u16 { + let v = v1.wrapping_sub(v0); + if v > 0x8000 { !v + 1 } else { v } + } + let [p0, p1] = points; + + // If the slope of the line is greater than 1. + if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) { + // Swap points 0 and 1 so that y0 is always smaller than y1. + let (x0, y0, x1, y1) = match points[0].y > points[1].y { + true => (points[1].x, points[1].y, points[0].x, points[0].y), + false => (points[0].x, points[0].y, points[1].x, points[1].y), + }; + + let dy = y1 - y0; + let (dx, xi) = match x0 > x1 { + true => (x0 - x1, 0xffff), + false => (x1 - x0, 0x0001), + }; + let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2); + let dx2 = dx * 2; + let mut d = dx2.wrapping_sub(dy); + let mut x = x0; + + for y in y0..=y1 { + self.draw_pixel(colour, layer, ScreenPosition::new(x, y)); + if d < 0x8000 { + x = x.wrapping_add(xi); + d = d.wrapping_add(dxdy2); + } else { + d = d.wrapping_add(dx2); + } + } + // If the slope of the line is less than or equal to 1. + } else { + // Swap points 0 and 1 so that x0 is always smaller than x1. + let (x0, y0, x1, y1) = match points[0].x > points[1].x { + true => (points[1].x, points[1].y, points[0].x, points[0].y), + false => (points[0].x, points[0].y, points[1].x, points[1].y), + }; + + let dx = x1 - x0; + let (dy, yi) = match y0 > y1 { + true => (y0 - y1, 0xffff), + false => (y1 - y0, 0x0001), + }; + let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2); + let dy2 = dy * 2; + let mut d = dy2.wrapping_sub(dx); + let mut y = y0; + + for x in x0..=x1 { + self.draw_pixel(colour, layer, ScreenPosition::new(x, y)); + if d < 0x8000 { + y = y.wrapping_add(yi); + d = d.wrapping_add(dydx2); + } else { + d = d.wrapping_add(dy2); + } + } + } + } + + fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + let [start, end] = points; + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + if (x0 >= dim.width && x1 >= dim.width) || start.y >= dim.height { return } + let x0 = min(x0, dim.width.saturating_sub(1)); + let x1 = min(x1, dim.width.saturating_sub(1)); + let row_i = (dim.width as usize) * (start.y as usize); + let start_i = row_i + x0 as usize; + let end_i = row_i + x1 as usize; + let layer = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + layer[start_i..=end_i].fill(colour); + return + } + + fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + let [start, end] = points; + let dim = self.dimensions; + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (y0 >= dim.height && y1 >= dim.height) || start.x >= dim.width { return } + let y0 = min(y0, dim.height.saturating_sub(1)); + let y1 = min(y1, dim.height.saturating_sub(1)); + let mut i = (start.x as usize) + (dim.width as usize * (y0 as usize)); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + pixels[i] = colour; + i += dim.width as usize; + } + return + } + + fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) { + let [start, end] = self.vector.get_pair(); + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return } + let x0 = min(x0, dim.width.saturating_sub(1)) as usize; + let x1 = min(x1, dim.width.saturating_sub(1)) as usize; + let y0 = min(y0, dim.height.saturating_sub(1)) as usize; + let y1 = min(y1, dim.height.saturating_sub(1)) as usize; + let width = x1 - x0 + 1; + let mut i = x0 + ((dim.width as usize) * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + pixels[i..i+width].fill(colour); + i += dim.width as usize; + } + } + + fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) { + let [start, end] = self.vector.get_pair(); + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return } + let x0 = min(x0, dim.width.saturating_sub(1)) as usize; + let x1 = min(x1, dim.width.saturating_sub(1)) as usize; + let y0 = min(y0, dim.height.saturating_sub(1)) as usize; + let y1 = min(y1, dim.height.saturating_sub(1)) as usize; + let width = x1 - x0 + 1; + let mut i = x0 + ((dim.width as usize) * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + + let sprite_data = self.sprite_data.get_1bit_sprite(); + let mut sprite_i = y0 % 8; + let sprite_x_off = (x0 % 8) as u32; + let transparent = params & 0x08 != 0; + if params & 0x07 != 0 { + todo!("Pre-treat sprite, with rotation/translation"); + } + + for _ in y0..=y1 { + let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off); + for _ in x0..=x1 { + let colour = (row >> 7) as usize; + if !(transparent && colour == 0) { + pixels[i] = self.sprite_colours[colour]; + } + row = row.rotate_left(1); + i += 1; + } + sprite_i = (sprite_i + 1) % 8; + i += (dim.width as usize) - width; + } + } + + fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) { + let transparent = params & 0x08 != 0; + let mut position = self.cursor; + let mut pointer: usize = 0; + + macro_rules! inc_x { ($v:expr) => { position.x = position.x.wrapping_add($v) }; } + macro_rules! dec_x { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; } + macro_rules! inc_y { ($v:expr) => { position.y = position.y.wrapping_add($v) }; } + macro_rules! dec_y { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; } + macro_rules! plot { () => { + let colour = sprite[pointer]; + if !(transparent && colour == 0) { + self.draw_pixel(self.sprite_colours[colour as usize], layer, position); + } + pointer += 1; + }; } + + match params & 0x07 { + 0x00 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); inc_y!(1); } } + 0x01 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); inc_y!(1); } } + 0x02 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); dec_y!(1); } } + 0x03 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); dec_y!(1); } } + + 0x04 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); inc_x!(1); } } + 0x05 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); dec_x!(1); } } + 0x06 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); inc_x!(1); } } + 0x07 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); dec_x!(1); } } + + _ => unreachable!(), + } + } +} |