summaryrefslogtreecommitdiff
path: root/src/devices/screen_device.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/screen_device.rs')
-rw-r--r--src/devices/screen_device.rs450
1 files changed, 450 insertions, 0 deletions
diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs
new file mode 100644
index 0000000..483bcca
--- /dev/null
+++ b/src/devices/screen_device.rs
@@ -0,0 +1,450 @@
+use crate::*;
+
+use geometry::*;
+use phosphor::*;
+
+
+pub type Sprite = [[u8; 8]; 8];
+
+#[derive(Clone, Copy)]
+pub enum Layer { Fg, Bg }
+
+
+pub struct ScreenDevice {
+ /// Each byte represents a screen pixel, left-to-right and top-to-bottom.
+ // Only the bottom four bits of each byte are used.
+ pub fg: Vec<u8>,
+ pub bg: Vec<u8>,
+ pub dirty: bool,
+
+ pub cursor: ScreenPosition,
+ pub vector: ScreenPosition,
+
+ pub dimensions: ScreenDimensions,
+ pub dirty_dimensions: bool,
+ pub width_write: u16,
+ pub height_write: u16,
+ pub fixed_width: Option<u16>,
+ pub fixed_height: Option<u16>,
+
+ pub palette_write: u16,
+ pub palette: [Colour; 16],
+ pub sprite_colours: u16,
+ pub sprite: SpriteBuffer,
+
+ pub accessed: bool,
+ pub wake: bool,
+}
+
+
+impl HasDimensions<u16> for ScreenDevice {
+ fn dimensions(&self) -> ScreenDimensions {
+ self.dimensions
+ }
+}
+
+
+impl Device for ScreenDevice {
+ fn read(&mut self, port: u8) -> u8 {
+ self.accessed = true;
+ match port {
+ 0x0 => read_h!(self.cursor.x),
+ 0x1 => read_l!(self.cursor.x),
+ 0x2 => read_h!(self.cursor.y),
+ 0x3 => read_l!(self.cursor.y),
+ 0x4 => read_h!(self.dimensions.width),
+ 0x5 => read_l!(self.dimensions.width),
+ 0x6 => read_h!(self.dimensions.height),
+ 0x7 => read_l!(self.dimensions.height),
+ 0x8 => 0,
+ 0x9 => 0,
+ 0xA => 0,
+ 0xB => 0,
+ 0xC => 0,
+ 0xD => 0,
+ 0xE => 0,
+ 0xF => 0,
+ _ => unreachable!(),
+ }
+ }
+
+ fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ let signal = if self.accessed { None } else { Some(Signal::Break) };
+ self.accessed = true;
+ match port {
+ 0x0 => write_h!(self.cursor.x, value),
+ 0x1 => write_l!(self.cursor.x, value),
+ 0x2 => write_h!(self.cursor.y, value),
+ 0x3 => write_l!(self.cursor.y, value),
+ 0x4 => write_h!(self.width_write, value),
+ 0x5 => { write_l!(self.width_write, value); self.resize_width() }
+ 0x6 => write_h!(self.height_write, value),
+ 0x7 => { write_l!(self.height_write, value); self.resize_height() }
+ 0x8 => write_h!(self.palette_write, value),
+ 0x9 => { write_l!(self.palette_write, value); self.set_palette() }
+ 0xA => write_h!(self.sprite_colours, value),
+ 0xB => write_l!(self.sprite_colours, value),
+ 0xC => self.sprite.push_byte(value),
+ 0xD => self.sprite.push_byte(value),
+ 0xE => self.draw_dispatch(value),
+ 0xF => self.move_cursor(value),
+ _ => unreachable!(),
+ };
+ return signal;
+ }
+
+ fn wake(&mut self) -> bool {
+ self.accessed = true;
+ std::mem::take(&mut self.wake)
+ }
+
+ fn reset(&mut self) {
+ self.fg.clear();
+ self.bg.clear();
+ self.dirty = false;
+
+ self.cursor = ScreenPosition::ZERO;
+ self.vector = ScreenPosition::ZERO;
+
+ self.dirty_dimensions = true;
+ self.width_write = 0;
+ self.height_write = 0;
+ self.fixed_width = None;
+ self.fixed_height = None;
+
+ self.palette_write = 0;
+ self.palette = [
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA),
+ ];
+ self.sprite_colours = 0;
+ self.sprite = SpriteBuffer::new();
+
+ self.accessed = false;
+ self.wake = false;
+ }
+}
+
+
+impl ScreenDevice {
+ pub fn new(config: &EmulatorConfig) -> Self {
+ let area = config.dimensions.area_usize();
+
+ Self {
+ fg: vec![0; area],
+ bg: vec![0; area],
+ dirty: false,
+
+ cursor: ScreenPosition::ZERO,
+ vector: ScreenPosition::ZERO,
+
+ dimensions: config.dimensions,
+ dirty_dimensions: true,
+ width_write: 0,
+ height_write: 0,
+ fixed_width: None,
+ fixed_height: None,
+
+ palette_write: 0,
+ palette: [Colour::BLACK; 16],
+ sprite_colours: 0,
+ sprite: SpriteBuffer::new(),
+
+ accessed: false,
+ wake: false,
+ }
+ }
+
+ /// Resize screen to match window dimensions.
+ pub fn resize(&mut self, dimensions: phosphor::Dimensions) {
+ // Replace dimensions with fixed dimensions.
+ let screen_dimensions = ScreenDimensions {
+ width: match self.fixed_width {
+ Some(fixed_width) => fixed_width,
+ None => dimensions.width as u16,
+ },
+ height: match self.fixed_height {
+ Some(fixed_height) => fixed_height,
+ None => dimensions.height as u16,
+ },
+ };
+ let old_dimensions = self.dimensions;
+ if self.dimensions != screen_dimensions {
+ self.dimensions = screen_dimensions;
+ self.resize_layers(old_dimensions);
+ self.wake = true;
+ }
+ }
+
+ /// Internal resize.
+ fn resize_width(&mut self) {
+ self.fixed_width = Some(self.width_write);
+ self.dirty_dimensions = true;
+ let old_dimensions = self.dimensions;
+ if self.dimensions.width != self.width_write {
+ self.dimensions.width = self.width_write;
+ self.resize_layers(old_dimensions);
+ }
+ }
+
+ /// Internal resize.
+ fn resize_height(&mut self) {
+ self.fixed_height = Some(self.height_write);
+ self.dirty_dimensions = true;
+ let old_dimensions = self.dimensions;
+ if self.dimensions.height != self.height_write {
+ self.dimensions.height = self.height_write;
+ self.resize_layers(old_dimensions);
+ }
+ }
+
+ fn resize_layers(&mut self, old_dimensions: ScreenDimensions) {
+ use std::cmp::{min, Ordering};
+
+ let old_width = old_dimensions.width as usize;
+ let old_height = old_dimensions.height as usize;
+ let new_width = self.dimensions.width as usize;
+ let new_height = self.dimensions.height as usize;
+ let new_area = self.dimensions.area_usize();
+ let y_range = 0..min(old_height, new_height);
+ let new_colour = match self.fg.last() {
+ None | Some(0) => *self.bg.last().unwrap_or(&0),
+ Some(colour) => *colour,
+ };
+
+ match new_width.cmp(&old_width) {
+ Ordering::Less => {
+ for y in y_range {
+ let src = y * old_width;
+ let dest = y * new_width;
+ let len = new_width;
+ self.fg.copy_within(src..src+len, dest);
+ self.bg.copy_within(src..src+len, dest);
+ }
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ },
+ Ordering::Greater => {
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ for y in y_range.rev() {
+ let src = y * old_width;
+ let dest = y * new_width;
+ let len = old_width;
+ self.fg.copy_within(src..src+len, dest);
+ self.bg.copy_within(src..src+len, dest);
+ self.fg[dest+len..dest+new_width].fill(0);
+ self.bg[dest+len..dest+new_width].fill(new_colour);
+ }
+ },
+ Ordering::Equal => {
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ },
+ };
+
+ self.dirty = true;
+ }
+
+ pub fn set_palette(&mut self) {
+ let i = (self.palette_write >> 12 ) as usize;
+ let r = (self.palette_write >> 8 & 0xF) as u8 * 17;
+ let g = (self.palette_write >> 4 & 0xF) as u8 * 17;
+ let b = (self.palette_write & 0xF) as u8 * 17;
+ let colour = Colour::from_rgb(r, g, b);
+ if self.palette[i] != colour {
+ self.palette[i] = colour;
+ self.dirty = true;
+ }
+ }
+
+ pub fn draw_dispatch(&mut self, draw: u8) {
+ match draw >> 4 {
+ 0x0 => self.op_draw_pixel(Layer::Bg, draw),
+ 0x1 => self.op_draw_sprite(Layer::Bg, draw),
+ 0x2 => self.op_fill_layer(Layer::Bg, draw),
+ 0x3 => self.op_draw_sprite(Layer::Bg, draw),
+ 0x4 => self.op_draw_line(Layer::Bg, draw),
+ 0x5 => self.op_draw_line(Layer::Bg, draw),
+ 0x6 => self.op_draw_rect(Layer::Bg, draw),
+ 0x7 => self.op_draw_rect(Layer::Bg, draw),
+ 0x8 => self.op_draw_pixel(Layer::Fg, draw),
+ 0x9 => self.op_draw_sprite(Layer::Fg, draw),
+ 0xA => self.op_fill_layer(Layer::Fg, draw),
+ 0xB => self.op_draw_sprite(Layer::Fg, draw),
+ 0xC => self.op_draw_line(Layer::Fg, draw),
+ 0xD => self.op_draw_line(Layer::Fg, draw),
+ 0xE => self.op_draw_rect(Layer::Fg, draw),
+ 0xF => self.op_draw_rect(Layer::Fg, draw),
+ _ => unreachable!(),
+ }
+ self.vector = self.cursor;
+ self.dirty = true;
+ }
+
+ pub fn move_cursor(&mut self, value: u8) {
+ let distance = (value & 0x3F) as u16;
+ match value >> 6 {
+ 0b00 => self.cursor.x = self.cursor.x.wrapping_add(distance),
+ 0b01 => self.cursor.y = self.cursor.y.wrapping_add(distance),
+ 0b10 => self.cursor.x = self.cursor.x.wrapping_sub(distance),
+ 0b11 => self.cursor.y = self.cursor.y.wrapping_sub(distance),
+ _ => unreachable!(),
+ };
+ }
+
+ /// Colour must already be masked by 0xF.
+ pub fn draw_pixel(&mut self, layer: Layer, x: u16, y: u16, colour: u8) {
+ if x < self.dimensions.width && y < self.dimensions.height {
+ let index = x as usize + (self.dimensions.width as usize * y as usize);
+ match layer {
+ Layer::Fg => self.fg[index] = colour,
+ Layer::Bg => self.bg[index] = colour,
+ };
+ }
+ }
+
+ fn op_draw_pixel(&mut self, layer: Layer, draw: u8) {
+ self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xF);
+ }
+
+ fn op_fill_layer(&mut self, layer: Layer, draw: u8) {
+ match layer {
+ Layer::Fg => self.fg.fill(draw & 0xF),
+ Layer::Bg => self.bg.fill(draw & 0xF),
+ }
+ }
+
+ fn op_draw_sprite(&mut self, layer: Layer, draw: u8) {
+ let sprite = match draw & 0x20 != 0 {
+ true => self.sprite.read_2bit_sprite(draw),
+ false => self.sprite.read_1bit_sprite(draw),
+ };
+ let colours = [
+ (self.sprite_colours >> 12 & 0x000F) as u8,
+ (self.sprite_colours >> 8 & 0x000F) as u8,
+ (self.sprite_colours >> 4 & 0x000F) as u8,
+ (self.sprite_colours & 0x000F) as u8,
+ ];
+ let cx = self.cursor.x;
+ let cy = self.cursor.y;
+
+ if draw & 0x08 != 0 {
+ // Draw sprite with transparent background
+ for y in 0..8 {
+ for x in 0..8 {
+ let index = sprite[y as usize][x as usize] as usize;
+ if index != 0 {
+ let px = cx.wrapping_add(x);
+ let py = cy.wrapping_add(y);
+ self.draw_pixel(layer, px, py, colours[index]);
+ }
+ }
+ }
+ } else {
+ // Draw sprite with opaque background
+ for y in 0..8 {
+ for x in 0..8 {
+ let index = sprite[y as usize][x as usize] as usize;
+ let px = cx.wrapping_add(x);
+ let py = cy.wrapping_add(y);
+ self.draw_pixel(layer, px, py, colours[index]);
+ }
+ }
+ }
+ }
+
+ fn op_draw_line(&mut self, layer: Layer, draw: u8) {
+ let mut x: i16 = self.cursor.x as i16;
+ let mut y: i16 = self.cursor.y as i16;
+ let x_end: i16 = self.vector.x as i16;
+ let y_end: i16 = self.vector.y as i16;
+
+ let dx: i32 = ((x_end as i32) - (x as i32)).abs();
+ let dy: i32 = -((y_end as i32) - (y as i32)).abs();
+ let sx: i16 = if x < x_end { 1 } else { -1 };
+ let sy: i16 = if y < y_end { 1 } else { -1 };
+ let mut e1: i32 = dx + dy;
+
+ if draw & 0x10 != 0 {
+ // Draw 1-bit textured line.
+ let sprite = self.sprite.read_1bit_sprite(draw);
+ let c1 = (self.sprite_colours >> 8 & 0xF) as u8;
+ let c0 = (self.sprite_colours >> 12 & 0xF) as u8;
+ let opaque = draw & 0x08 == 0;
+ loop {
+ let sprite_pixel = sprite[(y as usize) % 8][(x as usize) % 8];
+ if sprite_pixel != 0 { self.draw_pixel(layer, x as u16, y as u16, c1); }
+ else if opaque { self.draw_pixel(layer, x as u16, y as u16, c0); }
+ if x == x_end && y == y_end { break; }
+ let e2 = e1 << 1;
+ if e2 >= dy { e1 += dy; x += sx; }
+ if e2 <= dx { e1 += dx; y += sy; }
+ }
+ } else {
+ // Draw solid line.
+ let colour = draw & 0xF;
+ loop {
+ self.draw_pixel(layer, x as u16, y as u16, colour);
+ if x == x_end && y == y_end { break; }
+ let e2 = e1 << 1;
+ if e2 >= dy { e1 += dy; x += sx; }
+ if e2 <= dx { e1 += dx; y += sy; }
+ }
+ }
+ }
+
+ fn op_draw_rect(&mut self, layer: Layer, draw: u8) {
+ macro_rules! clamp {
+ ($v:expr, $max:expr) => {
+ if $v > 0x7FFF { 0 } else if $v > $max { $max } else { $v }
+ };
+ }
+ macro_rules! out_of_bounds {
+ ($axis:ident, $max:expr) => {{
+ let c = self.cursor.$axis;
+ let v = self.vector.$axis;
+ c >= $max && v >= $max && (c >= 0x8000) == (v >= 0x8000)
+ }};
+ }
+
+ let out_of_bounds_x = out_of_bounds!(x, self.dimensions.width);
+ let out_of_bounds_y = out_of_bounds!(y, self.dimensions.height);
+ if out_of_bounds_x || out_of_bounds_y { return; }
+
+ // Get bounding box.
+ let mut l = clamp!(self.vector.x, self.dimensions.width -1);
+ let mut r = clamp!(self.cursor.x, self.dimensions.width -1);
+ let mut t = clamp!(self.vector.y, self.dimensions.height -1);
+ let mut b = clamp!(self.cursor.y, self.dimensions.height -1);
+ if l > r { std::mem::swap(&mut l, &mut r) };
+ if t > b { std::mem::swap(&mut t, &mut b) };
+
+ if draw & 0x10 != 0 {
+ // Draw 1-bit textured rectangle.
+ let sprite = self.sprite.read_1bit_sprite(draw);
+ let c1 = (self.sprite_colours >> 8 & 0xF) as u8;
+ let c0 = (self.sprite_colours >> 12 & 0xF) as u8;
+ let opaque = draw & 0x08 == 0;
+ for y in t..=b {
+ for x in l..=r {
+ let sprite_colour = sprite[(y as usize) % 8][(x as usize) % 8];
+ if sprite_colour != 0 { self.draw_pixel(layer, x, y, c1); }
+ else if opaque { self.draw_pixel(layer, x, y, c0); }
+ }
+ }
+ } else {
+ // Draw solid rectangle.
+ let colour = draw & 0xF;
+ for y in t..=b {
+ for x in l..=r {
+ self.draw_pixel(layer, x, y, colour);
+ }
+ }
+ }
+ }
+}
+
+