diff options
Diffstat (limited to 'src/emulators/graphical_emulator.rs')
| -rw-r--r-- | src/emulators/graphical_emulator.rs | 363 | 
1 files changed, 182 insertions, 181 deletions
diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs index 1c58a34..c4b06c8 100644 --- a/src/emulators/graphical_emulator.rs +++ b/src/emulators/graphical_emulator.rs @@ -1,128 +1,30 @@  use crate::*; -use bedrock_core::*; - -use phosphor::*; - -use std::time::Instant; - - -pub struct GraphicalDeviceBus { -    pub sys: SystemDevice, -    pub mem: MemoryDevice, -    pub mat: MathDevice, -    pub clk: ClockDevice, -    pub inp: InputDevice, -    pub scr: ScreenDevice, -    pub loc: LocalDevice, -    pub rem: RemoteDevice, -    pub fs1: FileDevice, -    pub fs2: FileDevice, -} - -impl GraphicalDeviceBus { -    pub fn new(config: &EmulatorConfig) -> Self { -        Self { -            sys: SystemDevice::new(), -            mem: MemoryDevice::new(), -            mat: MathDevice::new(), -            clk: ClockDevice::new(), -            inp: InputDevice::new(), -            scr: ScreenDevice::new(config), -            loc: LocalDevice::new(config), -            rem: RemoteDevice::new(), -            fs1: FileDevice::new(), -            fs2: FileDevice::new(), -        } -    } - -    pub fn graphical(&self) -> bool { -        self.inp.accessed || self.scr.accessed -    } -} - -impl DeviceBus for GraphicalDeviceBus { -    fn read(&mut self, port: u8) -> u8 { -        match port & 0xf0 { -            0x00 => self.sys.read(port & 0x0f), -            0x10 => self.mem.read(port & 0x0f), -            0x20 => self.mat.read(port & 0x0f), -            0x30 => self.clk.read(port & 0x0f), -            0x40 => self.inp.read(port & 0x0f), -            0x50 => self.scr.read(port & 0x0f), -            0x80 => self.loc.read(port & 0x0f), -            0x90 => self.rem.read(port & 0x0f), -            0xa0 => self.fs1.read(port & 0x0f), -            0xb0 => self.fs2.read(port & 0x0f), -            _ => 0 -        } -    } - -    fn write(&mut self, port: u8, value: u8) -> Option<Signal> { -        match port & 0xf0 { -            0x00 => self.sys.write(port & 0x0f, value), -            0x10 => self.mem.write(port & 0x0f, value), -            0x20 => self.mat.write(port & 0x0f, value), -            0x30 => self.clk.write(port & 0x0f, value), -            0x40 => self.inp.write(port & 0x0f, value), -            0x50 => self.scr.write(port & 0x0f, value), -            0x80 => self.loc.write(port & 0x0f, value), -            0x90 => self.rem.write(port & 0x0f, value), -            0xa0 => self.fs1.write(port & 0x0f, value), -            0xb0 => self.fs2.write(port & 0x0f, value), -            _ => None -        } -    } - -    fn wake(&mut self) -> bool { -        macro_rules! rouse { -            ($id:expr, $dev:ident) => { -                if self.sys.can_wake($id) && self.$dev.wake() { -                    self.sys.wake_id = $id; -                    self.sys.asleep = false; -                    return true; -                } -            }; -        } -        rouse!(0xb, fs2); -        rouse!(0xa, fs1); -        rouse!(0x9, rem); -        rouse!(0x8, loc); -        rouse!(0x5, scr); -        rouse!(0x4, inp); -        rouse!(0x3, clk); -        rouse!(0x2, mat); -        rouse!(0x1, mem); -        rouse!(0x0, sys); -        return false; -    } -}  pub struct GraphicalEmulator {      pub br: BedrockEmulator<GraphicalDeviceBus>,      pub debug: DebugState, -    pub dimensions: ScreenDimensions, +      pub fullscreen: bool,      pub scale: u32,      pub render_mark: Instant, -    pub debug_palette: Option<[Colour; 16]>, -    pub show_debug_palette: bool, -    pub show_cursor: bool, +    pub show_override_palette: bool, + +    pub config: EmulatorConfig,  }  impl GraphicalEmulator { -    pub fn new(config: &EmulatorConfig, debug: bool) -> Self { -        let devices = GraphicalDeviceBus::new(config); +    pub fn new(config: EmulatorConfig, debug: bool) -> Self {          Self { -            br: BedrockEmulator::new(devices), +            br: BedrockEmulator::new(GraphicalDeviceBus::new(&config)),              debug: DebugState::new(debug, config.symbols_path.as_ref()), -            dimensions: config.dimensions, +              fullscreen: config.fullscreen, -            scale: config.scale, +            scale: config.zoom.into(), +            show_override_palette: config.override_palette.is_some(),              render_mark: Instant::now(), -            debug_palette: config.debug_palette, -            show_debug_palette: config.debug_palette.is_some(), -            show_cursor: config.show_cursor, + +            config,          }      } @@ -130,60 +32,47 @@ impl GraphicalEmulator {          self.br.core.mem.load_program(bytecode);      } -    pub fn run(&mut self) -> EmulatorSignal { -        loop { -            match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { -                Some(Signal::Fork) => { -                    self.br.core.mem.pc = 0; -                    self.br.core.wst.sp = 0; -                    self.br.core.rst.sp = 0; -                } -                Some(Signal::Sleep) => loop { -                    if self.br.dev.graphical() { -                        return EmulatorSignal::Promote; -                    } -                    if self.br.dev.wake() { break; } -                    std::thread::sleep(MIN_TICK_DURATION); -                } -                Some(Signal::Halt) => { -                    self.br.dev.loc.flush(); -                    log::info!("Program halted, exiting."); -                    self.debug.debug_summary(&self.br.core); -                    return EmulatorSignal::Halt; -                } -                Some(Signal::Debug1) => { -                    self.debug.debug_summary(&self.br.core); -                } -                _ => (), -            } +    pub fn run(self, mut phosphor: Phosphor) { +        let cursor = match self.config.show_cursor { +            true => Some(CursorIcon::Default), +            false => None, +        }; -            if self.br.dev.graphical() { -                return EmulatorSignal::Promote; +        let program_name = match &self.config.metadata { +            Some(metadata) => match &metadata.name { +                Some(name) => name.to_string(), +                None => String::from("Bedrock"),              } -        } +            None => String::from("Bedrock"), +        }; +        let window = WindowBuilder { +            dimensions: Some(self.dimensions()), +            size_bounds: Some(self.size_bounds()), +            fullscreen: self.fullscreen, +            scale: self.scale, +            title: Some(program_name), +            cursor, +            icon: parse_icon_from_config(&self.config), +            program: Box::new(self), +        }; + +        phosphor.create_window(window); +        phosphor.run().unwrap();      }      pub fn size_bounds(&self) -> SizeBounds { -        macro_rules! to_u32 { -            ($opt:expr) => { -                match $opt { -                    Some(a) => Some(u32::from(a)), -                    None => None, -                } -            }; -        }          match self.fullscreen {              true => SizeBounds { -                min_width: None, -                max_width: None, +                min_width:  None, +                max_width:  None,                  min_height: None,                  max_height: None,              },              false => SizeBounds { -                min_width: to_u32!(self.br.dev.scr.fixed_width), -                max_width: to_u32!(self.br.dev.scr.fixed_width), -                min_height: to_u32!(self.br.dev.scr.fixed_height), -                max_height: to_u32!(self.br.dev.scr.fixed_height), +                min_width:  self.br.dev.screen.fixed_width.map(u32::from), +                max_width:  self.br.dev.screen.fixed_width.map(u32::from), +                min_height: self.br.dev.screen.fixed_height.map(u32::from), +                max_height: self.br.dev.screen.fixed_height.map(u32::from),              },          } @@ -191,44 +80,121 @@ impl GraphicalEmulator {      pub fn dimensions(&self) -> Dimensions {          Dimensions { -            width: u32::from(self.br.dev.scr.dimensions.width), -            height: u32::from(self.br.dev.scr.dimensions.height), +            width: self.br.dev.screen.dimensions.width.into(), +            height: self.br.dev.screen.dimensions.height.into(), +        } +    } +} + + +pub struct GraphicalDeviceBus { +    pub system: SystemDevice, +    pub memory: MemoryDevice, +    pub math:   MathDevice, +    pub clock:  ClockDevice, +    pub input:  InputDevice, +    pub screen: ScreenDevice, +    pub stream: StreamDevice, +    pub file:   FileDevice, +} + +impl GraphicalDeviceBus { +    pub fn new(config: &EmulatorConfig) -> Self { +        Self { +            system: SystemDevice::new(0b1111_1100_1010_0000), +            memory: MemoryDevice::new(), +            math:   MathDevice::new(), +            clock:  ClockDevice::new(), +            input:  InputDevice::new(), +            screen: ScreenDevice::new(&config), +            stream: StreamDevice::new(&config), +            file:   FileDevice::new(),          }      }  } +impl DeviceBus for GraphicalDeviceBus { +    fn read(&mut self, port: u8) -> u8 { +        match port & 0xf0 { +            0x00 => self.system.read(port & 0x0f), +            0x10 => self.memory.read(port & 0x0f), +            0x20 => self.math  .read(port & 0x0f), +            0x30 => self.clock .read(port & 0x0f), +            0x40 => self.input .read(port & 0x0f), +            0x50 => self.screen.read(port & 0x0f), +            0x80 => self.stream.read(port & 0x0f), +            0xa0 => self.file  .read(port & 0x0f), +            _ => 0 +        } +    } + +    fn write(&mut self, port: u8, value: u8) -> Option<Signal> { +        match port & 0xf0 { +            0x00 => self.system.write(port & 0x0f, value), +            0x10 => self.memory.write(port & 0x0f, value), +            0x20 => self.math  .write(port & 0x0f, value), +            0x30 => self.clock .write(port & 0x0f, value), +            0x40 => self.input .write(port & 0x0f, value), +            0x50 => self.screen.write(port & 0x0f, value), +            0x80 => self.stream.write(port & 0x0f, value), +            0xa0 => self.file  .write(port & 0x0f, value), +            _ => None +        } +    } + +    fn wake(&mut self) -> bool { +        macro_rules! rouse { +            ($id:expr, $dev:ident) => { +                let is_eligible = test_bit!(self.system.wakers, 0x8000 >> $id); +                if is_eligible && self.$dev.wake() { +                    self.system.waker = $id; +                    self.system.asleep = false; +                    return true; +                } +            }; +        } +        rouse!(0xa, file  ); +        rouse!(0x8, stream); +        rouse!(0x5, screen); +        rouse!(0x4, input ); +        rouse!(0x3, clock ); +        return false; +    } +} + +  impl WindowProgram for GraphicalEmulator {      fn handle_event(&mut self, event: Event, r: &mut EventWriter<Request>) {          match event {              Event::CloseRequest => r.write(Request::CloseWindow), -            Event::CursorEnter => self.br.dev.inp.on_cursor_enter(), -            Event::CursorExit => self.br.dev.inp.on_cursor_exit(), -            Event::CursorMove(p) => self.br.dev.inp.on_cursor_move(p), -            Event::Resize(d) => self.br.dev.scr.resize(d), -            Event::CharacterInput(c) => self.br.dev.inp.on_character(c), -            Event::ModifierChange(m) => self.br.dev.inp.on_modifier(m), +            Event::CursorEnter => self.br.dev.input.on_cursor_enter(), +            Event::CursorExit => self.br.dev.input.on_cursor_exit(), +            Event::CursorMove(p) => self.br.dev.input.on_cursor_move(p), +            Event::Resize(d) => self.br.dev.screen.resize(d), +            Event::CharacterInput(c) => self.br.dev.input.on_character(c), +            Event::ModifierChange(m) => self.br.dev.input.on_modifier(m),              Event::MouseButton { button, action } => -                self.br.dev.inp.on_mouse_button(button, action), +                self.br.dev.input.on_mouse_button(button, action),              Event::FocusChange(_) => (),              Event::Initialise => (),              Event::ScrollLines { axis, distance } => match axis { -                Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance), -                Axis::Vertical   => self.br.dev.inp.on_vertical_scroll(distance), +                Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance), +                Axis::Vertical   => self.br.dev.input.on_vertical_scroll(distance),              }              Event::ScrollPixels { axis, distance } => match axis { -                Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance / 20.0), -                Axis::Vertical   => self.br.dev.inp.on_vertical_scroll(distance / 20.0), +                Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance / 20.0), +                Axis::Vertical   => self.br.dev.input.on_vertical_scroll(distance / 20.0),              }              Event::FileDrop(_path) => todo!("FileDrop"),              Event::Close => (),              Event::KeyboardInput { key, action } => { -                self.br.dev.inp.on_keypress(key, action); +                self.br.dev.input.on_keypress(key, action);                  if action == Action::Pressed {                      match key {                          KeyCode::F2 => { -                            self.show_debug_palette = !self.show_debug_palette; +                            self.show_override_palette = !self.show_override_palette;                              r.write(Request::Redraw);                          },                          KeyCode::F5 => { @@ -251,12 +217,12 @@ impl WindowProgram for GraphicalEmulator {      }      fn process(&mut self, requests: &mut EventWriter<Request>) { -        self.br.dev.loc.flush(); +        self.br.dev.stream.flush(); -        if self.br.dev.sys.asleep { +        if self.br.dev.system.asleep {              // Stay asleep if there are no pending wake events.              if !self.br.dev.wake() { -                if self.br.dev.scr.dirty { +                if self.br.dev.screen.dirty {                      requests.write(Request::Redraw);                  }                  std::thread::sleep(MIN_TICK_DURATION); @@ -264,7 +230,7 @@ impl WindowProgram for GraphicalEmulator {              }              // Wait for the current frame to be rendered. -            if self.br.dev.scr.dirty { +            if self.br.dev.screen.dirty {                  if self.render_mark.elapsed() > MIN_FRAME_DURATION {                      requests.write(Request::Redraw);                  } @@ -281,30 +247,30 @@ impl WindowProgram for GraphicalEmulator {                      todo!("Fork")                  }                  Some(Signal::Sleep) => { -                    self.br.dev.sys.asleep = true; +                    self.br.dev.system.asleep = true;                      break;                  }                  Some(Signal::Halt) => { -                    self.br.dev.loc.flush(); +                    self.br.dev.stream.flush();                      log::info!("Program halted, exiting.");                      self.debug.debug_summary(&self.br.core);                      requests.write(Request::CloseWindow);                      break;                  } -                Some(Signal::Debug1) => { +                Some(Signal::Debug(Debug::Debug1)) => {                      self.debug.debug_summary(&self.br.core);                  }                  _ => (),              }          } -        if std::mem::take(&mut self.br.dev.scr.dirty_dimensions) { +        if std::mem::take(&mut self.br.dev.screen.dirty_dimensions) {              requests.write(Request::SetSizeBounds(self.size_bounds()));          } -        if self.br.dev.scr.dirty { +        if self.br.dev.screen.dirty {              let elapsed = self.render_mark.elapsed(); -            if self.br.dev.sys.asleep && elapsed > MIN_FRAME_DURATION { +            if self.br.dev.system.asleep && elapsed > MIN_FRAME_DURATION {                  requests.write(Request::Redraw);              } else if elapsed > MAX_FRAME_DURATION {                  requests.write(Request::Redraw); @@ -315,15 +281,15 @@ impl WindowProgram for GraphicalEmulator {      }      fn render(&mut self, buffer: &mut Buffer, _full: bool) { -        let screen = &mut self.br.dev.scr; +        let screen = &mut self.br.dev.screen;          // Generate table for calculating pixel colours from layer values.          // A given screen pixel will be rendered as the colour given by          // table[fg][bg], where fg and bg are the corresponding layer values.          let mut table = [Colour::BLACK; 256]; -        let palette = match self.debug_palette { -            Some(debug_palette) => match self.show_debug_palette { -                true => debug_palette, +        let palette = match self.config.override_palette { +            Some(override_palette) => match self.show_override_palette { +                true => override_palette,                  false => screen.palette,              }              None => screen.palette, @@ -376,3 +342,38 @@ impl WindowProgram for GraphicalEmulator {          self.render_mark = Instant::now();      }  } + + +fn parse_icon_from_config(config: &EmulatorConfig) -> Option<Icon> { +    let metadata = config.metadata.as_ref()?; +    let bytes = metadata.small_icon.as_ref()?; +    let bg = metadata.bg_colour.unwrap_or(Colour::BLACK); +    let fg = metadata.bg_colour.unwrap_or(Colour::WHITE); +    let rgba = sprite_icon_to_rgb(bytes, 3, bg, fg); +    match Icon::from_rgba(rgba, 24, 24) { +        Ok(icon) => Some(icon), +        Err(err) => unreachable!("Error while parsing small icon data: {err}"), +    } +} + +fn sprite_icon_to_rgb(bytes: &[u8], size: usize, bg: Colour, fg: Colour) -> Vec<u8> { +    let sprites: Vec<&[u8]> = bytes.chunks_exact(8).collect(); +    let mut rgba = Vec::new(); +    for sprite_row in 0..size { +        for pixel_row in 0..8 { +            for sprite_column in 0..size { +                let sprite = &sprites[sprite_column + (sprite_row * size)]; +                let row = &sprite[pixel_row]; +                for bit in 0..8 { +                    let state = row & (0x80 >> bit); +                    let colour = match state != 0 { +                        true => fg, +                        false => bg, +                    }; +                    rgba.extend_from_slice(&colour.as_rgba_array()); +                } +            } +        } +    } +    return rgba; +}  | 
