diff --git a/Cargo.lock b/Cargo.lock index f03afcb..65ea43b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,6 +216,7 @@ version = "0.1.0" dependencies = [ "crsn", "libc", + "log", ] [[package]] @@ -366,22 +367,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "launcher_nox" -version = "0.1.0" -dependencies = [ - "anyhow", - "clappconfig", - "crsn", - "crsn_arith", - "crsn_buf", - "crsn_stdio", - "log", - "serde", - "simple_logger", - "thiserror", -] - [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 31d54f3..1f66ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ "launcher", - "launcher_nox", + #"launcher_nox", "crsn", "crsn_arith", "crsn_screen", diff --git a/README.md b/README.md index bc4ee51..fbc4ec9 100644 --- a/README.md +++ b/README.md @@ -738,7 +738,7 @@ To delete a buffer, use the `del` instruction - `(del @Obj)` This module uses the minifb rust crate to provide a framebuffer with key and mouse input. -Colors use the `RRGGBB` hex format. +Colors use the `0xRRGGBB` or `#RRGGBB` hex format. If input events are required, then make sure to periodically call `(sc-blit)` or `(sc-poll)`. This may not be needed if the auto-blit function is enabled and the display is regularly written. @@ -753,17 +753,17 @@ such as animations. ; Initialize the screen (opens a window) (sc-init WIDTH HEIGHT) -; Erase the screen (fill with black) -(sc-erase) -; Fill with a custom color -(sc-erase 0xFF00FF) - ; Set pixel color -(sc-px X Y COLOR) +(sc-wr Rd'x Rd'y Rd'color) ; legacy name: sc-px + +; Get pixel color +(sc-rd Wr'color Rd'x Rd'y) ; Set screen option. Constants are pre-defined. -; SCREEN_AUTO_BLIT (1) ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS) -; SCREEN_FPS (2) ... frame rate +; - SCREEN_AUTO_BLIT (1) ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS) +; - SCREEN_FPS (2) ......... frame rate +; - SCREEN_UPSCALE (3) ..... upscaling factor (big pixels). +; Scales coordinates for mouse and pixels and increases pixel area (sc-opt OPTION VALUE) ; Blit (render the pixel buffer). @@ -781,11 +781,24 @@ such as animations. ; Check key status. Keys are 0-127. Reads 1 if the key is pressed, 0 if not. ; A list of supported keys can be found in the extension source code. -(sc-key PRESSED KEY) +(sc-key Wr'pressed Rd'num) -; Check mouse button state +; Check mouse button state. ; 0-left, 1-right, 2-middle -(sc-mbtn PRESSED BTN) +(sc-mbtn Wr'pressed Rd'btn) +``` + +### Graphic acceleration commands + +``` +; Fill a rectangle +(sc-rect Rd'x Rd'y Rd'w Rd'h Rd'color) + +; Erase the screen (fill with black) +(sc-erase) + +; Fill with a custom color +(sc-erase Rd'color) ``` ## Stdio module diff --git a/crsn/src/asm/parse/arg_parser.rs b/crsn/src/asm/parse/arg_parser.rs index e1ba4a1..0177ad1 100644 --- a/crsn/src/asm/parse/arg_parser.rs +++ b/crsn/src/asm/parse/arg_parser.rs @@ -45,8 +45,12 @@ impl<'a> TokenParser<'a> { /// Get error if not empty. /// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string" pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> { + self.ensure_empty_custom(&format!("Instruction needs {}!", what_arguments)) + } + + pub fn ensure_empty_custom(&self, msg : &str) -> Result<(), CrsnError> { if self.have_more() { - Err(CrsnError::Parse(format!("Instruction needs {}!", what_arguments).into(), self.start_pos.clone())) + Err(CrsnError::Parse(msg.to_string().into(), self.start_pos.clone())) } else { Ok(()) } diff --git a/crsn/src/runtime/run_thread/state.rs b/crsn/src/runtime/run_thread/state.rs index 7e0f9a4..1315ea5 100644 --- a/crsn/src/runtime/run_thread/state.rs +++ b/crsn/src/runtime/run_thread/state.rs @@ -115,10 +115,12 @@ impl RunState { } /// Read a `Rd` value + #[inline] pub fn read(&mut self, rd: impl Into) -> Result { let rd = rd.into(); // TODO dedupe match rd.0 { + RdData::Immediate(v) => Ok(v), RdData::Register(Register::Gen(rn)) => { if likely(rn < REG_COUNT as u8) { trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]); @@ -135,7 +137,6 @@ impl RunState { Err(Fault::RegisterNotExist { reg: Register::Global(rn) }) } } - RdData::Immediate(v) => Ok(v), RdData::Register(Register::Arg(rn)) => { if likely(rn < REG_COUNT as u8) { trace!("Rd {:?} = {}", rd, self.frame.arg[rn as usize]); diff --git a/crsn_screen/src/defs.rs b/crsn_screen/src/defs.rs index 87c1d7d..cb7cb7e 100644 --- a/crsn_screen/src/defs.rs +++ b/crsn_screen/src/defs.rs @@ -12,6 +12,11 @@ pub enum ScreenOp { y: Rd, color: Rd, }, + GetPixel { + color: Wr, + x: Rd, + y: Rd, + }, GetMouse { x: Wr, y: Wr, @@ -28,6 +33,13 @@ pub enum ScreenOp { Erase { color: Rd, }, + FillRect { + x: Rd, + y: Rd, + w: Rd, + h: Rd, + color: Rd, + }, Blit { force: Rd, }, diff --git a/crsn_screen/src/exec.rs b/crsn_screen/src/exec.rs index 855d0f0..b8cb7cb 100644 --- a/crsn_screen/src/exec.rs +++ b/crsn_screen/src/exec.rs @@ -3,7 +3,9 @@ use std::time::{Duration, Instant}; use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions}; -use crsn::asm::data::literal::Value; +use crsn::asm::data::literal::{Value, is_negative}; +use crsn::asm::error::CrsnError; +use crsn::asm::instr::cond::Flag; use crsn::module::{EvalRes, OpTrait}; use crsn::runtime::fault::Fault; use crsn::runtime::run_thread::{state::RunState, ThreadInfo}; @@ -12,18 +14,21 @@ use crsn::sexp::Sexp; use crsn::utils::A; use crate::defs::ScreenOp; -use crsn::asm::instr::cond::Flag; +use std::mem; #[derive(Debug)] struct Opts { auto_blit: bool, frame_rate: Duration, + upscale: Value, } #[derive(Debug)] struct Backend { - width: usize, - height: usize, + width: Value, + height: Value, + log_width: Value, + log_height: Value, buffer: Vec, window: Option, last_render: Instant, @@ -35,19 +40,33 @@ impl Default for Backend { 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, mut x: Value, mut 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 {} @@ -81,8 +100,22 @@ impl OpTrait for ScreenOp { 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); + 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!() @@ -106,8 +139,9 @@ impl OpTrait for ScreenOp { match &mut backend.window { Some(w) => { if !w.is_open() { - // TODO... - std::process::exit(0); + debug!("Window is closed"); + let _ = backend.window.take(); + return Err(Fault::Halt); } w.update(); @@ -126,23 +160,46 @@ impl OpTrait for ScreenOp { let backend: &mut Backend = state.ext_mut(); - if x >= backend.width as u64 || y >= backend.height as u64 { + 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.width as u64 + x; + 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 { - backend.buffer[index as usize] = color as u32; - - if backend.opts.auto_blit { - blit_maybe(backend)?; - } + let c = backend.buffer[index as usize]; + trace!("index {}, c {}", index, c); + state.write(color, c as Value)?; } } None => { @@ -163,8 +220,11 @@ impl OpTrait for ScreenOp { state.set_flag(Flag::Overflow, true); } Some((xf, yf)) => { - let xval = xf.round() as u64; - let yval = yf.round() as u64; + 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)?; @@ -227,6 +287,51 @@ impl OpTrait for ScreenOp { } } } + 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) @@ -236,13 +341,17 @@ impl OpTrait for ScreenOp { 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::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)]) + } } } } @@ -266,8 +375,10 @@ fn init(state: &mut RunState, width: Value, height: Value) -> Result<(), Fault> return Err(Fault::NotAllowed("Screen already initialized".into())); } - backend.width = width as usize; - backend.height = height as usize; + 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); @@ -289,10 +400,12 @@ 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, backend.height) + w.update_with_buffer(&backend.buffer, backend.width as usize, backend.height as usize) .expect("Update screen"); // TODO fault backend.last_render = Instant::now(); diff --git a/crsn_screen/src/lib.rs b/crsn_screen/src/lib.rs index e87b6e7..c95e975 100644 --- a/crsn_screen/src/lib.rs +++ b/crsn_screen/src/lib.rs @@ -7,7 +7,7 @@ use crsn::asm::parse::arg_parser::TokenParser; use crsn::module::{CrsnExtension, ParseRes}; use crsn::sexp::SourcePosition; use crsn::asm::data::literal::Value; -use crate::exec::{OPT_AUTO_BLIT, OPT_FRAME_RATE}; +use crate::exec::{OPT_AUTO_BLIT, OPT_FRAME_RATE, OPT_UPSCALE}; mod defs; mod parse; @@ -38,6 +38,7 @@ impl CrsnExtension for ScreenOps { match name { "SCREEN_AUTO_BLIT" => Some(OPT_AUTO_BLIT), "SCREEN_FPS" => Some(OPT_FRAME_RATE), + "SCREEN_UPSCALE" => Some(OPT_UPSCALE), _ => None } } diff --git a/crsn_screen/src/parse.rs b/crsn_screen/src/parse.rs index 9f6fb78..79f3975 100644 --- a/crsn_screen/src/parse.rs +++ b/crsn_screen/src/parse.rs @@ -9,9 +9,10 @@ use crate::defs::ScreenOp; use super::exec::OPT_AUTO_BLIT; use super::exec::OPT_FRAME_RATE; +use super::exec::OPT_UPSCALE; pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenParser<'a>) -> Result, CrsnError> { - Ok(ParseRes::ext(match keyword { + let rv = Ok(ParseRes::ext(match keyword { "sc-init" => { ScreenOp::ScreenInit { width: args.next_rd()?, @@ -29,7 +30,7 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar } } - "sc-px" => { + "sc-wr" | "sc-px" => { ScreenOp::SetPixel { x: args.next_rd()?, y: args.next_rd()?, @@ -37,6 +38,14 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar } } + "sc-rd" => { + ScreenOp::GetPixel { + color: args.next_wr()?, + x: args.next_rd()?, + y: args.next_rd()?, + } + } + "sc-opt" => { let (val, valopt) = args.next_value()?; ScreenOp::SetOpt { @@ -47,6 +56,9 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar OPT_FRAME_RATE => { OPT_FRAME_RATE } + OPT_UPSCALE => { + OPT_UPSCALE + } _ => { return Err(CrsnError::Parse("Bad screen option".into(), valopt)); } @@ -90,8 +102,20 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar } } + "sc-rect" => { + ScreenOp::FillRect { + x: args.next_rd()?, + y: args.next_rd()?, + w: args.next_rd()?, + h: args.next_rd()?, + color: args.next_rd()? + } + } + _other => { return Ok(ParseRes::Unknown(args)); } - })) + })); + args.ensure_empty_custom("Too many arguments for this instruction!")?; + return rv; } diff --git a/crsn_stdio/Cargo.toml b/crsn_stdio/Cargo.toml index fa7469e..5a0d428 100644 --- a/crsn_stdio/Cargo.toml +++ b/crsn_stdio/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] crsn = { path = "../crsn" } libc = "0.2.79" +log = "0.4.11" diff --git a/crsn_stdio/src/lib.rs b/crsn_stdio/src/lib.rs index 5088df4..126cec4 100644 --- a/crsn_stdio/src/lib.rs +++ b/crsn_stdio/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate log; + use crsn::asm::data::literal::Value; use crsn::asm::error::CrsnError; use crsn::asm::instr::op::OpKind; @@ -134,6 +137,7 @@ impl StdioOps { impl Drop for StdioOps { fn drop(&mut self) { + debug!("stdin restore"); // Un-break the terminal if let Some(tio) = self.old_tio.take() { let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) }; diff --git a/examples/screen_pick_color.csn b/examples/screen_pick_color.csn new file mode 100644 index 0000000..843992d --- /dev/null +++ b/examples/screen_pick_color.csn @@ -0,0 +1,28 @@ +( + (sc-init 512 512) + (sc-opt SCREEN_UPSCALE 16) + (def W 32) + + (lds @cout "Drag colors to draw...\n") + + (sc-wr 0 0 #ff00ff) + (sc-wr 5 5 #ffff00) + (sc-wr 15 15 #00ff00) + (sc-blit) + + (:l) + (sc-poll) + (sc-mbtn r0 0) + (j.z :nope) + + (sc-mouse r0 r1) + (sc-rd r3 r0 r1) + (sub r0 1) + (sub r1 1) + (sc-rect r0 r1 3 3 r3) + (sc-blit) + + (:nope) + (mslp 10) + (j :l) +) diff --git a/examples/screen_upscale.csn b/examples/screen_upscale.csn new file mode 100644 index 0000000..1fcc2e8 --- /dev/null +++ b/examples/screen_upscale.csn @@ -0,0 +1,14 @@ +( + (sc-init 512 512) + (sc-opt SCREEN_UPSCALE 32) + + (sc-wr 0 0 #ff00ff) + (sc-wr 1 1 #ffff00) + (sc-wr 15 15 #00ff00) + (sc-blit) + + (:l) + (sc-poll) + (mslp 100) + (j :l) +) diff --git a/examples/screen_upscale_mouse.csn b/examples/screen_upscale_mouse.csn new file mode 100644 index 0000000..aeec7d9 --- /dev/null +++ b/examples/screen_upscale_mouse.csn @@ -0,0 +1,15 @@ +( + (sc-init 512 512) + (sc-opt SCREEN_UPSCALE 16) + (def W 32) + + (:l) + (sc-poll) + (sc-erase) + (sc-mouse r0 r1) + (sc-rect 0 r1 W 1 #ff0000) + (sc-rect r0 0 1 W #0066ff) + (sc-wr r0 r1 #ffffff) + (mslp 10) + (j :l) +)