fixes, graphic acceleration commands, upscaling pixels

pull/21/head
Ondřej Hruška 4 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 = [ dependencies = [
"crsn", "crsn",
"libc", "libc",
"log",
] ]
[[package]] [[package]]
@ -366,22 +367,6 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"

@ -1,7 +1,7 @@
[workspace] [workspace]
members = [ members = [
"launcher", "launcher",
"launcher_nox", #"launcher_nox",
"crsn", "crsn",
"crsn_arith", "crsn_arith",
"crsn_screen", "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. 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)`. 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. 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) ; Initialize the screen (opens a window)
(sc-init WIDTH HEIGHT) (sc-init WIDTH HEIGHT)
; Erase the screen (fill with black)
(sc-erase)
; Fill with a custom color
(sc-erase 0xFF00FF)
; Set pixel color ; 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. ; 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_AUTO_BLIT (1) ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS)
; SCREEN_FPS (2) ... frame rate ; - 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) (sc-opt OPTION VALUE)
; Blit (render the pixel buffer). ; 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. ; 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. ; 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 ; 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 ## Stdio module

@ -45,8 +45,12 @@ impl<'a> TokenParser<'a> {
/// Get error if not empty. /// Get error if not empty.
/// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string" /// 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> { 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() { 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 { } else {
Ok(()) Ok(())
} }

@ -115,10 +115,12 @@ impl RunState {
} }
/// Read a `Rd` value /// Read a `Rd` value
#[inline]
pub fn read(&mut self, rd: impl Into<Rd>) -> Result<Value, Fault> { pub fn read(&mut self, rd: impl Into<Rd>) -> Result<Value, Fault> {
let rd = rd.into(); let rd = rd.into();
// TODO dedupe // TODO dedupe
match rd.0 { match rd.0 {
RdData::Immediate(v) => Ok(v),
RdData::Register(Register::Gen(rn)) => { RdData::Register(Register::Gen(rn)) => {
if likely(rn < REG_COUNT as u8) { if likely(rn < REG_COUNT as u8) {
trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]); trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]);
@ -135,7 +137,6 @@ impl RunState {
Err(Fault::RegisterNotExist { reg: Register::Global(rn) }) Err(Fault::RegisterNotExist { reg: Register::Global(rn) })
} }
} }
RdData::Immediate(v) => Ok(v),
RdData::Register(Register::Arg(rn)) => { RdData::Register(Register::Arg(rn)) => {
if likely(rn < REG_COUNT as u8) { if likely(rn < REG_COUNT as u8) {
trace!("Rd {:?} = {}", rd, self.frame.arg[rn as usize]); trace!("Rd {:?} = {}", rd, self.frame.arg[rn as usize]);

@ -12,6 +12,11 @@ pub enum ScreenOp {
y: Rd, y: Rd,
color: Rd, color: Rd,
}, },
GetPixel {
color: Wr,
x: Rd,
y: Rd,
},
GetMouse { GetMouse {
x: Wr, x: Wr,
y: Wr, y: Wr,
@ -28,6 +33,13 @@ pub enum ScreenOp {
Erase { Erase {
color: Rd, color: Rd,
}, },
FillRect {
x: Rd,
y: Rd,
w: Rd,
h: Rd,
color: Rd,
},
Blit { Blit {
force: Rd, force: Rd,
}, },

@ -3,7 +3,9 @@ use std::time::{Duration, Instant};
use minifb::{Key, MouseButton, MouseMode, ScaleMode, Window, WindowOptions}; 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::module::{EvalRes, OpTrait};
use crsn::runtime::fault::Fault; use crsn::runtime::fault::Fault;
use crsn::runtime::run_thread::{state::RunState, ThreadInfo}; use crsn::runtime::run_thread::{state::RunState, ThreadInfo};
@ -12,18 +14,21 @@ use crsn::sexp::Sexp;
use crsn::utils::A; use crsn::utils::A;
use crate::defs::ScreenOp; use crate::defs::ScreenOp;
use crsn::asm::instr::cond::Flag; use std::mem;
#[derive(Debug)] #[derive(Debug)]
struct Opts { struct Opts {
auto_blit: bool, auto_blit: bool,
frame_rate: Duration, frame_rate: Duration,
upscale: Value,
} }
#[derive(Debug)] #[derive(Debug)]
struct Backend { struct Backend {
width: usize, width: Value,
height: usize, height: Value,
log_width: Value,
log_height: Value,
buffer: Vec<u32>, buffer: Vec<u32>,
window: Option<Window>, window: Option<Window>,
last_render: Instant, last_render: Instant,
@ -35,19 +40,33 @@ impl Default for Backend {
Self { Self {
width: 0, width: 0,
height: 0, height: 0,
log_width: 0,
log_height: 0,
buffer: vec![], buffer: vec![],
window: None, window: None,
last_render: Instant::now().sub(Duration::from_secs(1)), last_render: Instant::now().sub(Duration::from_secs(1)),
opts: Opts { opts: Opts {
auto_blit: true, auto_blit: true,
upscale: 1,
frame_rate: Duration::from_micros(16600), 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_AUTO_BLIT: u64 = 1;
pub const OPT_FRAME_RATE: u64 = 2; pub const OPT_FRAME_RATE: u64 = 2;
pub const OPT_UPSCALE: u64 = 3;
// Hack for Window // Hack for Window
unsafe impl std::marker::Send for Backend {} unsafe impl std::marker::Send for Backend {}
@ -81,9 +100,23 @@ impl OpTrait for ScreenOp {
debug!("Set auto blit to {:?}", backend.opts.auto_blit); debug!("Set auto blit to {:?}", backend.opts.auto_blit);
} }
OPT_FRAME_RATE => { 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); backend.opts.frame_rate = Duration::from_micros(1_000_000 as u64 / val);
debug!("Set frame rate to {:?}", backend.opts.frame_rate); 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 => { _other => {
unreachable!() unreachable!()
} }
@ -106,8 +139,9 @@ impl OpTrait for ScreenOp {
match &mut backend.window { match &mut backend.window {
Some(w) => { Some(w) => {
if !w.is_open() { if !w.is_open() {
// TODO... debug!("Window is closed");
std::process::exit(0); let _ = backend.window.take();
return Err(Fault::Halt);
} }
w.update(); w.update();
@ -126,23 +160,46 @@ impl OpTrait for ScreenOp {
let backend: &mut Backend = state.ext_mut(); 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); state.set_flag(Flag::Overflow, true);
return Ok(eres); return Ok(eres);
} }
match &mut backend.window { match &mut backend.window {
Some(_w) => { 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() { if index as usize > backend.buffer.len() {
warn!("Screen set pixel out of bounds"); warn!("Screen set pixel out of bounds");
state.set_flag(Flag::Invalid, true); state.set_flag(Flag::Invalid, true);
} else { } else {
backend.buffer[index as usize] = color as u32; let c = backend.buffer[index as usize];
trace!("index {}, c {}", index, c);
if backend.opts.auto_blit { state.write(color, c as Value)?;
blit_maybe(backend)?;
}
} }
} }
None => { None => {
@ -163,8 +220,11 @@ impl OpTrait for ScreenOp {
state.set_flag(Flag::Overflow, true); state.set_flag(Flag::Overflow, true);
} }
Some((xf, yf)) => { Some((xf, yf)) => {
let xval = xf.round() as u64; let mut xval = xf.round() as u64;
let yval = yf.round() as u64; let mut yval = yf.round() as u64;
xval /= backend.opts.upscale;
yval /= backend.opts.upscale;
state.write(x, xval)?; state.write(x, xval)?;
state.write(y, yval)?; 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) Ok(eres)
@ -236,13 +341,17 @@ impl OpTrait for ScreenOp {
match self { match self {
ScreenOp::SetOpt { opt, val } => sexp::list(&[A("sc-opt"), A(opt), A(val)]), 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::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::Blit { force } => sexp::list(&[A("sc-blit"), A(force)]),
ScreenOp::Update => sexp::list(&[A("sc-poll")]), ScreenOp::Update => sexp::list(&[A("sc-poll")]),
ScreenOp::GetMouse { x, y } => sexp::list(&[A("sc-mouse"), A(x), A(y)]), 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::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::TestMouse { pressed, button } => sexp::list(&[A("sc-mbtn"), A(pressed), A(button)]),
ScreenOp::Erase { color } => sexp::list(&[A("sc-erase"), A(color)]), 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())); return Err(Fault::NotAllowed("Screen already initialized".into()));
} }
backend.width = width as usize; backend.width = width;
backend.height = height as usize; backend.height = height;
backend.log_width = width;
backend.log_height = height;
backend.buffer = vec![0; (width * height) as usize]; backend.buffer = vec![0; (width * height) as usize];
backend.window = Some(window); backend.window = Some(window);
@ -289,10 +400,12 @@ fn blit(backend: &mut Backend) -> Result<(), Fault> {
let w = backend.window.as_mut().unwrap(); let w = backend.window.as_mut().unwrap();
if !w.is_open() { if !w.is_open() {
debug!("Window is closed");
let _ = backend.window.take();
return Err(Fault::Halt); 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 .expect("Update screen"); // TODO fault
backend.last_render = Instant::now(); backend.last_render = Instant::now();

@ -7,7 +7,7 @@ use crsn::asm::parse::arg_parser::TokenParser;
use crsn::module::{CrsnExtension, ParseRes}; use crsn::module::{CrsnExtension, ParseRes};
use crsn::sexp::SourcePosition; use crsn::sexp::SourcePosition;
use crsn::asm::data::literal::Value; 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 defs;
mod parse; mod parse;
@ -38,6 +38,7 @@ impl CrsnExtension for ScreenOps {
match name { match name {
"SCREEN_AUTO_BLIT" => Some(OPT_AUTO_BLIT), "SCREEN_AUTO_BLIT" => Some(OPT_AUTO_BLIT),
"SCREEN_FPS" => Some(OPT_FRAME_RATE), "SCREEN_FPS" => Some(OPT_FRAME_RATE),
"SCREEN_UPSCALE" => Some(OPT_UPSCALE),
_ => None _ => None
} }
} }

@ -9,9 +9,10 @@ use crate::defs::ScreenOp;
use super::exec::OPT_AUTO_BLIT; use super::exec::OPT_AUTO_BLIT;
use super::exec::OPT_FRAME_RATE; 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> { 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" => { "sc-init" => {
ScreenOp::ScreenInit { ScreenOp::ScreenInit {
width: args.next_rd()?, 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 { ScreenOp::SetPixel {
x: args.next_rd()?, x: args.next_rd()?,
y: 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" => { "sc-opt" => {
let (val, valopt) = args.next_value()?; let (val, valopt) = args.next_value()?;
ScreenOp::SetOpt { 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_FRAME_RATE OPT_FRAME_RATE
} }
OPT_UPSCALE => {
OPT_UPSCALE
}
_ => { _ => {
return Err(CrsnError::Parse("Bad screen option".into(), valopt)); 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 => { _other => {
return Ok(ParseRes::Unknown(args)); return Ok(ParseRes::Unknown(args));
} }
})) }));
args.ensure_empty_custom("Too many arguments for this instruction!")?;
return rv;
} }

@ -9,3 +9,4 @@ edition = "2018"
[dependencies] [dependencies]
crsn = { path = "../crsn" } crsn = { path = "../crsn" }
libc = "0.2.79" 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::data::literal::Value;
use crsn::asm::error::CrsnError; use crsn::asm::error::CrsnError;
use crsn::asm::instr::op::OpKind; use crsn::asm::instr::op::OpKind;
@ -134,6 +137,7 @@ impl StdioOps {
impl Drop for StdioOps { impl Drop for StdioOps {
fn drop(&mut self) { fn drop(&mut self) {
debug!("stdin restore");
// Un-break the terminal // Un-break the terminal
if let Some(tio) = self.old_tio.take() { if let Some(tio) = self.old_tio.take() {
let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) }; 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