fixes, graphic acceleration commands, upscaling pixels

pull/21/head
Ondřej Hruška 3 years ago
parent 9faeceba61
commit 562c6baddc
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 17
      Cargo.lock
  2. 2
      Cargo.toml
  3. 37
      README.md
  4. 6
      crsn/src/asm/parse/arg_parser.rs
  5. 3
      crsn/src/runtime/run_thread/state.rs
  6. 12
      crsn_screen/src/defs.rs
  7. 151
      crsn_screen/src/exec.rs
  8. 3
      crsn_screen/src/lib.rs
  9. 30
      crsn_screen/src/parse.rs
  10. 1
      crsn_stdio/Cargo.toml
  11. 4
      crsn_stdio/src/lib.rs
  12. 28
      examples/screen_pick_color.csn
  13. 14
      examples/screen_upscale.csn
  14. 15
      examples/screen_upscale_mouse.csn

17
Cargo.lock generated

@ -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"

@ -1,7 +1,7 @@
[workspace]
members = [
"launcher",
"launcher_nox",
#"launcher_nox",
"crsn",
"crsn_arith",
"crsn_screen",

@ -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

@ -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(())
}

@ -115,10 +115,12 @@ impl RunState {
}
/// Read a `Rd` value
#[inline]
pub fn read(&mut self, rd: impl Into<Rd>) -> Result<Value, Fault> {
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]);

@ -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,
},

@ -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<u32>,
window: Option<Window>,
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,9 +100,23 @@ impl OpTrait for ScreenOp {
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!()
}
@ -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();

@ -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
}
}

@ -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<ParseRes<'a, OpKind>, 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;
}

@ -9,3 +9,4 @@ edition = "2018"
[dependencies]
crsn = { path = "../crsn" }
libc = "0.2.79"
log = "0.4.11"

@ -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) };

@ -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)
)

@ -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)
)

@ -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)
)
Loading…
Cancel
Save