summaryrefslogblamecommitdiff
path: root/src/devices/screen_device.rs
blob: a10ab203a85ab28353e15c5afb9622a66f7aca84 (plain) (tree)














































































































































































































































































                                                                                   
                                                           
                                                     
                                  






                                                            
                                                                              




















































                                                                                        
                                                                                   














































































                                                                                 
                                     





                          
                         




                                               
                           
     




                                                            


















                                                                                                        
                                                  

                      




                                                            



















                                                                                                                                               
                                                  

                      
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: 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.colours >>  8 & 0xf) as u8;
            let c0 = (self.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.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 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);
                }
            }
        }
    }
}

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,
    pub cached: Option<(Sprite, u8)>,
}

impl SpriteBuffer {
    pub fn new() -> Self {
        Self {
            mem: [0; 16],
            pointer: 0,
            cached: None,
        }
    }

    pub fn push_byte(&mut self, byte: u8) {
        self.mem[self.pointer] = byte;
        self.pointer = (self.pointer + 1) % 16;
        self.cached = None;
    }

    pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite {
        if let Some((sprite, transform)) = self.cached {
            if transform == (draw & 0x77) {
                return 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!(),
        }
        self.cached = Some((sprite, draw & 0x77));
        return sprite;
    }

    pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite {
        if let Some((sprite, transform)) = self.cached {
            if transform == (draw & 0x77) {
                return 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!(),
        }
        self.cached = Some((sprite, draw & 0x77));
        return sprite;
    }
}