forked from MightyPork/crsn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
16 KiB
534 lines
16 KiB
use std::ops::Sub;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions};
|
|
|
|
use crsn::asm::data::literal::{Value, is_negative};
|
|
use crsn::asm::instr::cond::Flag;
|
|
use crsn::module::{EvalRes, OpTrait};
|
|
use crsn::runtime::fault::Fault;
|
|
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
|
|
use crsn::sexp;
|
|
use crsn::sexp::Sexp;
|
|
use crsn::utils::A;
|
|
|
|
use crate::defs::ScreenOp;
|
|
use std::mem;
|
|
|
|
#[derive(Debug)]
|
|
struct Opts {
|
|
auto_blit: bool,
|
|
frame_rate: Duration,
|
|
upscale: Value,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Backend {
|
|
width: Value,
|
|
height: Value,
|
|
log_width: Value,
|
|
log_height: Value,
|
|
buffer: Vec<u32>,
|
|
window: Option<Window>,
|
|
last_render: Instant,
|
|
opts: Opts,
|
|
}
|
|
|
|
impl Default for Backend {
|
|
fn default() -> Self {
|
|
Self {
|
|
width: 0,
|
|
height: 0,
|
|
log_width: 0,
|
|
log_height: 0,
|
|
buffer: vec![],
|
|
window: None,
|
|
last_render: Instant::now().sub(Duration::from_secs(1)),
|
|
opts: Opts {
|
|
auto_blit: true,
|
|
upscale: 1,
|
|
frame_rate: Duration::from_micros(16600),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Backend {
|
|
pub fn draw_rect_log(&mut self, x: Value, y: Value, w: Value, h: Value, color: u32) {
|
|
for yy in (y * self.opts.upscale)..((y + h) * self.opts.upscale).min(self.height) {
|
|
for xx in (x * self.opts.upscale)..((x + w) * self.opts.upscale).min(self.width) {
|
|
self.buffer[(yy * self.width + xx) as usize] = color as u32;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const OPT_AUTO_BLIT: u64 = 1;
|
|
pub const OPT_FRAME_RATE: u64 = 2;
|
|
pub const OPT_UPSCALE: u64 = 3;
|
|
|
|
// Hack for Window
|
|
unsafe impl std::marker::Send for Backend {}
|
|
|
|
impl OpTrait for ScreenOp {
|
|
fn execute(&self, _info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
|
|
let eres = EvalRes::default();
|
|
match self {
|
|
ScreenOp::ScreenInit { width, height } => {
|
|
let w = state.read(width)?;
|
|
let h = state.read(height)?;
|
|
init(state, w, h)?;
|
|
}
|
|
|
|
ScreenOp::Erase { color } => {
|
|
let color = (state.read(color)? & 0xffffff) as u32;
|
|
let backend: &mut Backend = state.ext_mut();
|
|
for n in 0..(backend.buffer.len()) {
|
|
backend.buffer[n] = color;
|
|
}
|
|
}
|
|
|
|
ScreenOp::SetOpt { opt, val } => {
|
|
state.clear_status();
|
|
let val = state.read(val)?;
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
match *opt {
|
|
OPT_AUTO_BLIT => {
|
|
backend.opts.auto_blit = val != 0;
|
|
debug!("Set auto blit to {:?}", backend.opts.auto_blit);
|
|
}
|
|
OPT_FRAME_RATE => {
|
|
if val == 0 {
|
|
state.set_flag(Flag::Invalid, true);
|
|
} else {
|
|
backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val);
|
|
debug!("Set frame rate to {:?}", backend.opts.frame_rate);
|
|
}
|
|
}
|
|
OPT_UPSCALE => {
|
|
if val == 0 {
|
|
state.set_flag(Flag::Invalid, true);
|
|
} else {
|
|
backend.opts.upscale = val;
|
|
backend.log_width = backend.width / val;
|
|
backend.log_height = backend.height / val;
|
|
debug!("Set upscale to {:?}", val);
|
|
}
|
|
}
|
|
_other => {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::Blit { force } => {
|
|
let force = state.read(force)?;
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
if force != 0 {
|
|
blit(backend)?;
|
|
} else {
|
|
blit_maybe(backend)?;
|
|
}
|
|
}
|
|
|
|
ScreenOp::Update => {
|
|
let backend: &mut Backend = state.ext_mut();
|
|
match &mut backend.window {
|
|
Some(w) => {
|
|
if !w.is_open() {
|
|
debug!("Window is closed");
|
|
let _ = backend.window.take();
|
|
return Err(Fault::Halt);
|
|
}
|
|
|
|
w.update();
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::SetPixel { x, y, color } => {
|
|
state.clear_status();
|
|
let x = state.read(x)?;
|
|
let y = state.read(y)?;
|
|
let color = state.read(color)?;
|
|
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
if x >= backend.log_width as u64 || y >= backend.log_height as u64 {
|
|
state.set_flag(Flag::Overflow, true);
|
|
return Ok(eres);
|
|
}
|
|
|
|
match &mut backend.window {
|
|
Some(_w) => {
|
|
backend.draw_rect_log(x, y, 1, 1, color as u32);
|
|
if backend.opts.auto_blit {
|
|
blit_maybe(backend)?;
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::GetPixel { color, x, y } => {
|
|
state.clear_status();
|
|
let x = state.read(x)?;
|
|
let y = state.read(y)?;
|
|
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
if x >= backend.log_width as u64 || y >= backend.log_height as u64 {
|
|
state.set_flag(Flag::Overflow, true);
|
|
return Ok(eres);
|
|
}
|
|
|
|
match &mut backend.window {
|
|
Some(_w) => {
|
|
let index = y * backend.opts.upscale * backend.width as u64 + x * backend.opts.upscale;
|
|
if index as usize > backend.buffer.len() {
|
|
warn!("Screen set pixel out of bounds");
|
|
state.set_flag(Flag::Invalid, true);
|
|
} else {
|
|
let c = backend.buffer[index as usize];
|
|
trace!("index {}, c {}", index, c);
|
|
state.write(color, c as Value)?;
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::GetMouse { x, y } => {
|
|
state.clear_status();
|
|
let backend: &mut Backend = state.ext_mut();
|
|
match &mut backend.window {
|
|
Some(w) => {
|
|
let mp = w.get_mouse_pos(MouseMode::Discard);
|
|
debug!("mp = {:?}", mp);
|
|
match mp {
|
|
None => {
|
|
state.set_flag(Flag::Overflow, true);
|
|
}
|
|
Some((xf, yf)) => {
|
|
let mut xval = xf.round() as u64;
|
|
let mut yval = yf.round() as u64;
|
|
|
|
xval /= backend.opts.upscale;
|
|
yval /= backend.opts.upscale;
|
|
|
|
state.write(x, xval)?;
|
|
state.write(y, yval)?;
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::TestKey { pressed, code } => {
|
|
state.clear_status();
|
|
let num = num2key(state.read(code)?);
|
|
let backend: &mut Backend = state.ext_mut();
|
|
match &mut backend.window {
|
|
Some(w) => {
|
|
match num {
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
Some(kn) => {
|
|
let down = w.is_key_down(kn) as u64;
|
|
state.write(pressed, down)?;
|
|
state.update_status(down);
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScreenOp::TestMouse { pressed, button } => {
|
|
state.clear_status();
|
|
|
|
let omb = match state.read(button)? {
|
|
0 => Some(MouseButton::Left),
|
|
1 => Some(MouseButton::Right),
|
|
2 => Some(MouseButton::Middle),
|
|
_ => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
None
|
|
}
|
|
};
|
|
|
|
let backend: &mut Backend = state.ext_mut();
|
|
match &mut backend.window {
|
|
Some(w) => {
|
|
if let Some(mb) = omb {
|
|
let is_pressed = w.get_mouse_down(mb) as u64;
|
|
state.write(pressed, is_pressed)?;
|
|
state.update_status(is_pressed);
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
ScreenOp::FillRect {
|
|
x, y,
|
|
w, h,
|
|
color
|
|
} => {
|
|
let mut x = state.read(x)?;
|
|
let mut y = state.read(y)?;
|
|
let mut w = state.read(w)?;
|
|
let mut h = state.read(h)?;
|
|
let c = state.read(color)?;
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
if is_negative(w) || is_negative(h) {
|
|
state.set_flag(Flag::Invalid, true);
|
|
return Ok(eres);
|
|
}
|
|
if w > backend.log_width {
|
|
w = backend.log_width;
|
|
}
|
|
if h > backend.log_height {
|
|
h = backend.log_height;
|
|
}
|
|
|
|
if is_negative(x) {
|
|
let xi : i64 = unsafe { mem::transmute(x) };
|
|
let minus = xi.abs() as u64;
|
|
w -= minus;
|
|
x = 0;
|
|
}
|
|
if is_negative(y) {
|
|
let yi : i64 = unsafe { mem::transmute(y) };
|
|
let minus = yi.abs() as u64;
|
|
h -= minus;
|
|
y = 0;
|
|
}
|
|
|
|
match &mut backend.window {
|
|
Some(_w) => {
|
|
backend.draw_rect_log(x, y, w, h, c as u32);
|
|
}
|
|
None => {
|
|
state.set_flag(Flag::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(eres)
|
|
}
|
|
|
|
fn to_sexp(&self) -> Sexp {
|
|
match self {
|
|
ScreenOp::SetOpt { opt, val } => sexp::list(&[A("sc-opt"), A(opt), A(val)]),
|
|
ScreenOp::ScreenInit { width, height } => sexp::list(&[A("sc-init"), A(width), A(height)]),
|
|
ScreenOp::SetPixel { x, y, color } => sexp::list(&[A("sc-wr"), A(x), A(y), A(color)]),
|
|
ScreenOp::GetPixel { color, x, y } => sexp::list(&[A("sc-rd"), A(color), A(x), A(y)]),
|
|
ScreenOp::Blit { force } => sexp::list(&[A("sc-blit"), A(force)]),
|
|
ScreenOp::Update => sexp::list(&[A("sc-poll")]),
|
|
ScreenOp::GetMouse { x, y } => sexp::list(&[A("sc-mouse"), A(x), A(y)]),
|
|
ScreenOp::TestKey { pressed, code } => sexp::list(&[A("sc-key"), A(pressed), A(code)]),
|
|
ScreenOp::TestMouse { pressed, button } => sexp::list(&[A("sc-mbtn"), A(pressed), A(button)]),
|
|
ScreenOp::Erase { color } => sexp::list(&[A("sc-erase"), A(color)]),
|
|
ScreenOp::FillRect { x, y, w, h, color } => {
|
|
sexp::list(&[A("sc-rect"), A(x), A(y), A(w), A(h), A(color)])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn init(state: &mut RunState, width: Value, height: Value) -> Result<(), Fault> {
|
|
let window = Window::new(
|
|
"Croissant",
|
|
width as usize,
|
|
height as usize,
|
|
WindowOptions {
|
|
resize: true,
|
|
scale_mode: ScaleMode::UpperLeft,
|
|
..WindowOptions::default()
|
|
},
|
|
).expect("Unable to create window"); // TODO fault
|
|
|
|
|
|
let backend: &mut Backend = state.ext_mut();
|
|
|
|
if backend.window.is_some() {
|
|
return Err(Fault::NotAllowed("Screen already initialized".into()));
|
|
}
|
|
|
|
backend.width = width;
|
|
backend.height = height;
|
|
backend.log_width = width;
|
|
backend.log_height = height;
|
|
backend.buffer = vec![0; (width * height) as usize];
|
|
|
|
backend.window = Some(window);
|
|
|
|
blit_maybe(backend)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn blit_maybe(backend: &mut Backend) -> Result<(), Fault> {
|
|
if backend.last_render.elapsed() >= backend.opts.frame_rate {
|
|
blit(backend)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn blit(backend: &mut Backend) -> Result<(), Fault> {
|
|
let w = backend.window.as_mut().unwrap();
|
|
|
|
if !w.is_open() {
|
|
debug!("Window is closed");
|
|
let _ = backend.window.take();
|
|
return Err(Fault::Halt);
|
|
}
|
|
|
|
w.update_with_buffer(&backend.buffer, backend.width as usize, backend.height as usize)
|
|
.expect("Update screen"); // TODO fault
|
|
|
|
backend.last_render = Instant::now();
|
|
Ok(())
|
|
}
|
|
|
|
fn num2key(num: Value) -> Option<Key> {
|
|
let remap = [
|
|
Key::Key0, // 0
|
|
Key::Key1,
|
|
Key::Key2,
|
|
Key::Key3,
|
|
Key::Key4,
|
|
Key::Key5,
|
|
Key::Key6,
|
|
Key::Key7,
|
|
Key::Key8,
|
|
Key::Key9, // 9
|
|
|
|
Key::A, // 10
|
|
Key::B,
|
|
Key::C,
|
|
Key::D,
|
|
Key::E,
|
|
Key::F, // 15
|
|
Key::G,
|
|
Key::H,
|
|
Key::I,
|
|
Key::J,
|
|
Key::K, // 20
|
|
Key::L,
|
|
Key::M,
|
|
Key::N,
|
|
Key::O,
|
|
Key::P, // 25
|
|
Key::Q,
|
|
Key::R,
|
|
Key::S,
|
|
Key::T,
|
|
Key::U, // 30
|
|
Key::V,
|
|
Key::W,
|
|
Key::X,
|
|
Key::Y,
|
|
Key::Z, // 35
|
|
|
|
Key::F1, // 36
|
|
Key::F2,
|
|
Key::F3,
|
|
Key::F4,
|
|
Key::F5, // 40
|
|
Key::F6,
|
|
Key::F7,
|
|
Key::F8,
|
|
Key::F9,
|
|
Key::F10, // 45
|
|
Key::F11,
|
|
Key::F12,
|
|
Key::F13,
|
|
Key::F14,
|
|
Key::F15, // 50
|
|
|
|
Key::Down, // 51
|
|
Key::Left, // 52
|
|
Key::Right, // 53
|
|
Key::Up, // 54
|
|
Key::Apostrophe,
|
|
Key::Backquote, // 56 (a backtick)
|
|
Key::Backslash, // 57
|
|
Key::Comma,
|
|
Key::Equal,
|
|
Key::LeftBracket, // 60 [
|
|
Key::Minus,
|
|
Key::Period,
|
|
Key::RightBracket, // 63 ] *This does not work due to a bug in the library we use - PR is sent upstream https://github.com/emoon/rust_minifb/pull/222*
|
|
Key::Semicolon,
|
|
Key::Slash, // 65
|
|
Key::Backspace,
|
|
Key::Delete,
|
|
Key::End,
|
|
Key::Enter,
|
|
Key::Escape, // 70
|
|
Key::Home,
|
|
Key::Insert,
|
|
Key::Menu,
|
|
Key::PageDown,
|
|
Key::PageUp,
|
|
Key::Pause, // 76
|
|
Key::Space,
|
|
Key::Tab,
|
|
Key::NumLock,
|
|
Key::CapsLock,
|
|
Key::ScrollLock,
|
|
Key::LeftShift,
|
|
Key::RightShift,
|
|
Key::LeftCtrl,
|
|
Key::RightCtrl,
|
|
Key::NumPad0, // 86
|
|
Key::NumPad1,
|
|
Key::NumPad2,
|
|
Key::NumPad3,
|
|
Key::NumPad4,
|
|
Key::NumPad5,
|
|
Key::NumPad6,
|
|
Key::NumPad7,
|
|
Key::NumPad8,
|
|
Key::NumPad9,
|
|
Key::NumPadDot,
|
|
Key::NumPadSlash,
|
|
Key::NumPadAsterisk,
|
|
Key::NumPadMinus,
|
|
Key::NumPadPlus,
|
|
Key::NumPadEnter, // 100
|
|
|
|
Key::LeftAlt,
|
|
Key::RightAlt,
|
|
Key::LeftSuper,
|
|
Key::RightSuper,
|
|
];
|
|
|
|
if num < remap.len() as u64 {
|
|
Some(remap[num as usize])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
|