summaryrefslogtreecommitdiff
path: root/src/devices/screen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/screen.rs')
-rw-r--r--src/devices/screen.rs469
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!(),
+ }
+ }
+}