diff options
Diffstat (limited to 'src/devices/screen_device.rs')
-rw-r--r-- | src/devices/screen_device.rs | 485 |
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; + } +} |