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.rs485
1 files changed, 485 insertions, 0 deletions
diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs
new file mode 100644
index 0000000..64f3815
--- /dev/null
+++ b/src/devices/screen_device.rs
@@ -0,0 +1,485 @@
+use crate::*;
+
+use bedrock_core::*;
+use geometry::*;
+use phosphor::*;
+
+type Sprite = [[u8; 8]; 8];
+#[derive(Clone, Copy)]
+pub enum Layer { Fg, Bg }
+
+
+pub struct ScreenDevice {
+ pub wake: bool,
+ pub accessed: bool,
+
+ /// Each byte represents a screen pixel, left-to-right and top-to-bottom.
+ // Only the bottom four bits of each byte are used.
+ // TODO: Consider using the high bit of each pixel byte as a dirty bit.
+ 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 colours: u16,
+ pub sprite: SpriteBuffer,
+}
+
+impl ScreenDevice {
+ pub fn new(config: &EmulatorConfig) -> Self {
+ let area = config.dimensions.area_usize();
+
+ Self {
+ wake: false,
+ accessed: false,
+
+ 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],
+ colours: 0,
+ sprite: SpriteBuffer::new(),
+ }
+ }
+
+ /// External resize.
+ 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.colours >> 12 & 0x000f) as u8,
+ (self.colours >> 8 & 0x000f) as u8,
+ (self.colours >> 4 & 0x000f) as u8,
+ (self.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: i16 = (x_end - x).abs();
+ let dy: i16 = -(y_end - y).abs();
+ let sx: i16 = if x < x_end { 1 } else { -1 };
+ let sy: i16 = if y < y_end { 1 } else { -1 };
+ let mut e1: i16 = dx + dy;
+
+ if draw & 0x10 != 0 {
+ // Draw 1-bit textured line.
+ let sprite = self.sprite.read_1bit_sprite(draw);
+ let c1 = (self.colours >> 8 & 0xf) as u8;
+ let c0 = (self.colours >> 12 & 0xf) as u8;
+ let opaque = draw & 0x08 == 0;
+ loop {
+ let sprite_pixel = sprite[(y % 8) as usize][(x % 8) as usize];
+ 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.colours >> 8 & 0xf) as u8;
+ let c0 = (self.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 % 8) as usize][(x % 8) as usize];
+ 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);
+ }
+ }
+ }
+ }
+}
+
+impl Device for ScreenDevice {
+ fn read(&mut self, port: u8) -> u8 {
+ self.accessed = true;
+ match port {
+ 0x0 => read_h!(self.dimensions.width),
+ 0x1 => read_l!(self.dimensions.width),
+ 0x2 => read_h!(self.dimensions.height),
+ 0x3 => read_l!(self.dimensions.height),
+ 0x4 => read_h!(self.cursor.x),
+ 0x5 => read_l!(self.cursor.x),
+ 0x6 => read_h!(self.cursor.y),
+ 0x7 => read_l!(self.cursor.y),
+ 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> {
+ self.accessed = true;
+ match port {
+ 0x0 => write_h!(self.width_write, value),
+ 0x1 => { write_l!(self.width_write, value); self.resize_width(); },
+ 0x2 => write_h!(self.height_write, value),
+ 0x3 => { write_l!(self.height_write, value); self.resize_height(); },
+ 0x4 => write_h!(self.cursor.x, value),
+ 0x5 => write_l!(self.cursor.x, value),
+ 0x6 => write_h!(self.cursor.y, value),
+ 0x7 => write_l!(self.cursor.y, value),
+ 0x8 => write_h!(self.palette_write, value),
+ 0x9 => { write_l!(self.palette_write, value); self.set_palette(); },
+ 0xa => write_h!(self.colours, value),
+ 0xb => write_l!(self.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 None;
+ }
+
+ fn wake(&mut self) -> bool {
+ self.accessed = true;
+ std::mem::take(&mut self.wake)
+ }
+}
+
+impl HasDimensions<u16> for ScreenDevice {
+ fn dimensions(&self) -> ScreenDimensions {
+ self.dimensions
+ }
+}
+
+
+pub struct SpriteBuffer {
+ pub mem: [u8; 16],
+ pub pointer: usize,
+}
+
+impl SpriteBuffer {
+ pub fn new() -> Self {
+ Self {
+ mem: [0; 16],
+ pointer: 0,
+ }
+ }
+
+ pub fn push_byte(&mut self, byte: u8) {
+ self.mem[self.pointer] = byte;
+ self.pointer = (self.pointer + 1) % 16;
+ }
+
+ pub fn read_1bit_sprite(&self, draw: u8) -> Sprite {
+ macro_rules! c {
+ ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); };
+ ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; };
+ }
+ let mut sprite = [[0; 8]; 8];
+ let mut p = match draw & 0x02 != 0 {
+ true => self.pointer,
+ false => self.pointer + 8,
+ };
+ match draw & 0x07 {
+ 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } },
+ 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } },
+ 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } },
+ 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } },
+ 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } },
+ 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } },
+ 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } },
+ 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } },
+ _ => unreachable!(),
+ }
+ return sprite;
+ }
+
+ pub fn read_2bit_sprite(&self, draw: u8) -> Sprite {
+ macro_rules! c {
+ ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); };
+ ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; };
+ }
+ let mut sprite = [[0; 8]; 8];
+ let mut p = match draw & 0x02 != 0 {
+ true => self.pointer,
+ false => self.pointer + 8,
+ };
+ let mut s = p + 8;
+ match draw & 0x07 {
+ 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } },
+ _ => unreachable!(),
+ }
+ return sprite;
+ }
+}