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.
185 lines
5.5 KiB
185 lines
5.5 KiB
use std::ops::Sub;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use minifb::{ScaleMode, Window, WindowOptions};
|
|
|
|
use crsn::asm::data::literal::Value;
|
|
use crsn::asm::instr::Cond;
|
|
use crsn::module::{EvalRes, OpTrait};
|
|
use crsn::runtime::fault::Fault;
|
|
use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
|
|
|
|
use crate::defs::ScreenOp;
|
|
use crsn::sexp::Sexp;
|
|
use crsn::sexp;
|
|
use crsn::utils::A;
|
|
|
|
#[derive(Debug)]
|
|
struct Opts {
|
|
auto_blit: bool,
|
|
frame_rate: Duration,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Backend {
|
|
width: usize,
|
|
height: usize,
|
|
buffer: Vec<u32>,
|
|
window: Option<Window>,
|
|
last_render: Instant,
|
|
opts: Opts,
|
|
}
|
|
|
|
impl Default for Backend {
|
|
fn default() -> Self {
|
|
Self {
|
|
width: 0,
|
|
height: 0,
|
|
buffer: vec![],
|
|
window: None,
|
|
last_render: Instant::now().sub(Duration::from_secs(1)),
|
|
opts: Opts {
|
|
auto_blit: true,
|
|
frame_rate: Duration::from_micros(16600),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
const OPT_AUTO_BLIT: u64 = 1;
|
|
const OPT_FRAME_RATE: u64 = 2;
|
|
|
|
// 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::SetOpt { opt, val } => {
|
|
let opt = state.read(*opt)?;
|
|
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 => {
|
|
backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val);
|
|
debug!("Set frame rate to {:?}", backend.opts.frame_rate);
|
|
}
|
|
other => {
|
|
warn!("Bad screen opt: {}", other);
|
|
state.set_flag(Cond::Invalid, true);
|
|
}
|
|
}
|
|
}
|
|
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::SetPixel { x, y, color } => {
|
|
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.width as u64 || y >= backend.height as u64 {
|
|
state.set_flag(Cond::Overflow, true);
|
|
return Ok(eres);
|
|
}
|
|
|
|
match &mut backend.window {
|
|
Some(_w) => {
|
|
let index = y * backend.width as u64 + x;
|
|
if index as usize > backend.buffer.len() {
|
|
warn!("Screen set pixel out of bounds");
|
|
state.set_flag(Cond::Invalid, true);
|
|
} else {
|
|
backend.buffer[index as usize] = color as u32;
|
|
|
|
if backend.opts.auto_blit {
|
|
blit_maybe(backend);
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
state.set_flag(Cond::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-px"), A(x), A(y), A(color)]),
|
|
ScreenOp::Blit { force } => sexp::list(&[A("sc-blit"), A(force)])
|
|
}
|
|
}
|
|
}
|
|
|
|
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 as usize;
|
|
backend.height = height as usize;
|
|
backend.buffer = vec![0; (width * height) as usize];
|
|
|
|
// window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
|
|
// window.limit_update_rate(None);
|
|
|
|
backend.window = Some(window);
|
|
|
|
blit_maybe(backend);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn blit_maybe(backend: &mut Backend) {
|
|
if backend.last_render.elapsed() >= backend.opts.frame_rate {
|
|
blit(backend);
|
|
}
|
|
}
|
|
|
|
fn blit(backend: &mut Backend) {
|
|
backend.window.as_mut().unwrap()
|
|
.update_with_buffer(&backend.buffer, backend.width, backend.height)
|
|
.expect("Update screen"); // TODO fault
|
|
|
|
backend.last_render = Instant::now();
|
|
}
|
|
|