diff options
Diffstat (limited to 'src/devices/screen.rs')
-rw-r--r-- | src/devices/screen.rs | 344 |
1 files changed, 67 insertions, 277 deletions
diff --git a/src/devices/screen.rs b/src/devices/screen.rs index c96d1c4..4394b6c 100644 --- a/src/devices/screen.rs +++ b/src/devices/screen.rs @@ -1,37 +1,50 @@ mod sprite_data; -mod vector_points; +mod draw_line; +mod draw_rect; +mod draw_sprite; 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>; +pub type Plane = [u8; 8]; +pub type Sprite = [[u8; 8]; 8]; + +const TRANSPARENT: u8 = 0x08; +const FLIP_DIAGONAL: u8 = 0x04; +const FLIP_VERTICAL: u8 = 0x02; +const FLIP_HORIZONTAL: u8 = 0x01; +const NEGATIVE: u8 = 0x80; +const VERTICAL: u8 = 0x40; + #[derive(Copy, Clone)] pub enum ScreenLayer { Background, Foreground } +macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } + + pub struct ScreenDevice { pub wake_flag: bool, - /// Each byte represents a screen pixel, left-right-top-bottom. + + /// Each byte represents a screen pixel, left-to-right and top-to-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 dirty: bool, + pub resizable: bool, pub cursor: ScreenPosition, + pub vector: ScreenPosition, pub dimensions: ScreenDimensions, - pub sprite_data: SpriteData, - pub palette: [Colour; 16], + pub palette_high: u8, - pub sprite_colours: [u8; 4], - pub vector: VectorPoints, + pub palette: [Colour; 16], + pub sprite_buffer: SpriteBuffer, } impl ScreenDevice { @@ -41,25 +54,25 @@ impl ScreenDevice { foreground: Vec::new(), background: Vec::new(), - is_dirty: false, - is_resizable: true, + dirty: false, + resizable: true, cursor: ScreenPosition::ZERO, + vector: ScreenPosition::ZERO, dimensions: ScreenDimensions::ZERO, - sprite_data: SpriteData::new(), - palette: [Colour::BLACK; 16], - palette_high: 0, - sprite_colours: [0; 4], - vector: VectorPoints::new(), + palette_high: 0, + palette: [Colour::BLACK; 16], + sprite_buffer: SpriteBuffer::new(), } } - pub fn set_size(&mut self, dimensions: ScreenDimensions) { - self.is_resizable = false; - self.resize(dimensions); + pub fn set_size(&mut self) { + self.resizable = false; + self.resize(self.dimensions); } + // Resize the screen buffers while preserving the current content. pub fn resize(&mut self, dimensions: ScreenDimensions) { let old_width = self.dimensions.width as usize; let old_height = self.dimensions.height as usize; @@ -100,19 +113,20 @@ impl ScreenDevice { }; self.dimensions = dimensions; - self.is_dirty = true; + self.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() { + let mut lookup = [Colour::BLACK; 256]; + for (i, c) in lookup.iter_mut().enumerate() { match i > 0x0f { true => *c = self.palette[i >> 4], false => *c = self.palette[i & 0x0f], } }; + // Prepare values let b_width = buffer.width() as usize; let b_height = buffer.height() as usize; let s_width = self.dimensions.width() as usize; @@ -123,8 +137,9 @@ impl ScreenDevice { 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]; + *b = lookup[(fg << 4 | bg) as usize]; } + // Write colours to the buffer when the size of the buffer is wrong } else { let width = min(b_width, s_width); let height = min(b_height, s_height); @@ -139,17 +154,15 @@ impl ScreenDevice { &self.foreground[si..si+width], ); for (b, (bg, fg)) in zip(b_iter, s_iter) { - *b = palette[(fg << 4 | bg) as usize]; + *b = lookup[(fg << 4 | bg) as usize]; } - b_slice[bi+width..bi+width+width_excess].fill(palette[0]); + b_slice[bi+width..bi+width+width_excess].fill(lookup[0]); bi += b_width; si += s_width; } - b_slice[bi..].fill(palette[0]); + b_slice[bi..].fill(lookup[0]); } - - // Set flags - self.is_dirty = false; + self.dirty = false; } pub fn set_palette_high(&mut self, val: u8) { @@ -162,24 +175,14 @@ impl ScreenDevice { 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; + self.dirty = true; } pub fn shunt(&mut self, val: u8) { - let is_negative = val & 0x80 != 0; - let is_vertical = val & 0x40 != 0; + let negative = test!(val, NEGATIVE); + let vertical = test!(val, VERTICAL); let dist = (val & 0x3f) as u16; - match (is_negative, is_vertical) { + match (negative, 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), @@ -188,31 +191,32 @@ impl ScreenDevice { } 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 operation = val & 0x70; + let parameters = 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 textured line"), - 0x70 => self.draw_rect_1bit(params, layer), + match operation { + 0x00 => self.draw_pixel(parameters, layer, self.cursor), + 0x10 => self.draw_sprite_1bit(parameters, layer), + 0x20 => self.fill_layer(parameters, layer), + 0x30 => self.draw_sprite_2bit(parameters, layer), + 0x40 => self.draw_line(parameters, layer), + 0x50 => self.draw_rect(parameters, layer), + 0x60 => self.draw_line_1bit(parameters, layer), + 0x70 => self.draw_rect_1bit(parameters, layer), _ => unreachable!(), }; + + self.dirty = true; + self.vector = self.cursor; } + // Draw a single pixel of a single colour fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) { let dim = self.dimensions; - if !dim.contains_point(point) { return } + if !dim.contains_point(point) || colour > 0xf { return } let index = point.x as usize + ((dim.width as usize) * (point.y as usize)); match layer { ScreenLayer::Background => self.background[index] = colour, @@ -220,6 +224,7 @@ impl ScreenDevice { }; } + // Fill an entire screen layer with a single colour fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) { match layer { ScreenLayer::Background => self.background.fill(colour), @@ -227,227 +232,12 @@ impl ScreenDevice { } } - 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 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 [p0, p1] = self.vector.get_pair(); - match (p0.x == p1.x, p0.y == p1.y) { - (false, false) => self.draw_diagonal_line(colour, layer), - (false, true) => self.draw_horizontal_line(colour, layer), - ( true, false) => self.draw_vertical_line(colour, layer), - ( true, true) => self.draw_pixel(colour, layer, p0), - }; - } - - fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) { - fn abs_diff(v0: u16, v1: u16) -> u16 { - let v = v1.wrapping_sub(v0); - if v > 0x8000 { !v + 1 } else { v } - } - let [p0, p1] = self.vector.get_pair(); - - // 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 p0.y > p1.y { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.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 p0.x > p1.x { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.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) { - if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let i = screen_width * y; - let layer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - layer[i+x0..=i+x1].fill(colour); - } - } - - fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) { - if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let mut i = (screen_width * y0) + x; - let layer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - layer[i] = colour; - i += screen_width; - } - - } - } - - fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) { - if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * y0); - let pixels = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - pixels[i..i+rect_width].fill(colour); - i += screen_width; - } - } - } - - fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) { - if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * 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 += screen_width - rect_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! for8 { ($block:block) => { for _ in 0..8 { $block } }; } - macro_rules! r { ($v:expr) => { position.x = position.x.wrapping_add($v) }; } - macro_rules! l { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; } - macro_rules! d { ($v:expr) => { position.y = position.y.wrapping_add($v) }; } - macro_rules! u { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; } - macro_rules! px { () => { - let colour = sprite[pointer]; - if !(transparent && colour == 0) { - self.draw_pixel(self.sprite_colours[colour as usize], layer, position); - } - pointer += 1; - }; } - - - match params & 0x07 { - 0 => { for8!{{ for8!{{ px!(); r!(1); }} l!(8); d!(1); }} } - 1 => { r!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); d!(1); }} } - 2 => { d!(7); for8!{{ for8!{{ px!(); r!(1); }} l!(8); u!(1); }} } - 3 => { r!(7); d!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); u!(1); }} } - - 4 => { for8!{{ for8!{{ px!(); d!(1); }} u!(8); r!(1); }} } - 5 => { r!(7); for8!{{ for8!{{ px!(); d!(1); }} u!(8); l!(1); }} } - 6 => { d!(7);for8!{{ for8!{{ px!(); u!(1); }} d!(8); r!(1); }} } - 7 => { r!(7); d!(7); for8!{{ for8!{{ px!(); u!(1); }} d!(8); l!(1); }} } - - _ => unreachable!(), - } - } - - /// Returns [x0, y0, x1, y1] + /// Returns [x0, y0, x1, y1], ensuring that x0 <= x1 and y0 <= y1 fn find_vector_bounding_box(&self) -> Option<[usize; 4]> { macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} - let [p0, p1] = self.vector.get_pair(); + let [p0, p1] = [self.cursor, self.vector]; let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; |