mirror of
https://github.com/JanNeuendorf/SVC16.git
synced 2025-06-04 18:45:27 +00:00
Port to pixels crate (#5)
Using "pixels" instead of minifb allows for better window decorations and resizing. I mostly copied their winit exampe. The debug option has been removed and the scaling option is now a plain factor.
This commit is contained in:
parent
8d3db1a1c3
commit
3cbeba00ee
1809
Cargo.lock
generated
1809
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "svc16"
|
name = "svc16"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Jan Neuendorf"]
|
authors = ["Jan Neuendorf"]
|
||||||
description = "An emulator for a simple virtual computer"
|
description = "An emulator for a simple virtual computer"
|
||||||
@ -9,5 +9,8 @@ license="MIT"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.93"
|
anyhow = "1.0.93"
|
||||||
clap = { version = "4.5.21", features = ["derive"] }
|
clap = { version = "4.5.21", features = ["derive"] }
|
||||||
minifb = "0.27.0"
|
# There seems to be some incompatibility with the latest crates.io version of pixels?
|
||||||
|
pixels = { git = "https://github.com/parasyte/pixels.git", rev = "d4df286"}
|
||||||
thiserror = "2.0.3"
|
thiserror = "2.0.3"
|
||||||
|
winit = "0.29.5"
|
||||||
|
winit_input_helper = "0.16.0"
|
||||||
|
25
src/cli.rs
25
src/cli.rs
@ -5,17 +5,23 @@ use clap::Parser;
|
|||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
pub program: String,
|
pub program: String,
|
||||||
|
|
||||||
#[arg(short, long, default_value = "1", help = "Set the window scaling")]
|
#[arg(short, long, default_value = "1", help = "Set initial window scaling")]
|
||||||
pub scaling: usize,
|
pub scaling: u32,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
long,
|
long,
|
||||||
default_value = "3000000",
|
default_value_t = false,
|
||||||
help = "Set the maximum instructions per frame"
|
help = "Show cursor on the window"
|
||||||
)]
|
)]
|
||||||
pub max_ipf: usize,
|
pub cursor: bool,
|
||||||
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
default_value_t = false,
|
||||||
|
help = "Start in fullscreen mode"
|
||||||
|
)]
|
||||||
|
pub fullscreen: bool,
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
long,
|
long,
|
||||||
@ -26,9 +32,8 @@ pub struct Cli {
|
|||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
long,
|
long,
|
||||||
help = "Output instructions and value at the given address"
|
default_value = "3000000",
|
||||||
|
help = "Change the maximum instructions per frame"
|
||||||
)]
|
)]
|
||||||
pub debug: Option<Vec<u16>>,
|
pub max_ipf: usize,
|
||||||
#[arg(short, long, help = "Show cursor on window")]
|
|
||||||
pub cursor: bool,
|
|
||||||
}
|
}
|
||||||
|
@ -67,9 +67,6 @@ impl Engine {
|
|||||||
pub fn wants_to_sync(&self) -> bool {
|
pub fn wants_to_sync(&self) -> bool {
|
||||||
return self.sync_called;
|
return self.sync_called;
|
||||||
}
|
}
|
||||||
pub fn get_instruction_pointer(&self) -> u16 {
|
|
||||||
self.instruction_pointer
|
|
||||||
}
|
|
||||||
pub fn set_input(&mut self, pos_code: u16, key_code: u16) {
|
pub fn set_input(&mut self, pos_code: u16, key_code: u16) {
|
||||||
self.pos_code = pos_code;
|
self.pos_code = pos_code;
|
||||||
self.key_code = key_code;
|
self.key_code = key_code;
|
||||||
|
197
src/main.rs
197
src/main.rs
@ -1,138 +1,101 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod engine;
|
mod engine;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
use engine::Engine;
|
use engine::Engine;
|
||||||
use minifb::{Key, Scale, Window, WindowOptions};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use std::time::Instant;
|
use std::time::{Duration, Instant};
|
||||||
|
use utils::*;
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
use winit::keyboard::KeyCode;
|
||||||
|
use winit::window::WindowBuilder;
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
const RES: usize = 256;
|
const RES: usize = 256;
|
||||||
|
const FRAMETIME: Duration = Duration::from_nanos((1000000000. / 30.) as u64);
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
let initial_state = read_u16s_from_file(&cli.program)?;
|
let initial_state = read_u16s_from_file(&cli.program)?;
|
||||||
let mut engine = Engine::new(initial_state);
|
let mut engine = Engine::new(initial_state);
|
||||||
let mut options = WindowOptions::default();
|
|
||||||
|
|
||||||
options.scale = match cli.scaling {
|
let event_loop = EventLoop::new()?;
|
||||||
1 => Scale::X1,
|
let mut input = WinitInputHelper::new();
|
||||||
2 => Scale::X2,
|
if cli.scaling < 1 {
|
||||||
4 => Scale::X4,
|
return Err(anyhow!("The minimal scaling factor is 1"));
|
||||||
8 => Scale::X8,
|
}
|
||||||
16 => Scale::X16,
|
let window = {
|
||||||
_ => return Err(anyhow!("Scaling must be 1,2,4,8 or 16")),
|
let size = LogicalSize::new(
|
||||||
|
(RES as u32 * cli.scaling) as f64,
|
||||||
|
(RES as u32 * cli.scaling) as f64,
|
||||||
|
);
|
||||||
|
let min_size = LogicalSize::new((RES) as f64, (RES) as f64);
|
||||||
|
WindowBuilder::new()
|
||||||
|
.with_title("SVC16")
|
||||||
|
.with_inner_size(size)
|
||||||
|
.with_min_inner_size(min_size)
|
||||||
|
.build(&event_loop)?
|
||||||
|
};
|
||||||
|
window.set_cursor_visible(cli.cursor);
|
||||||
|
if cli.fullscreen {
|
||||||
|
window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
|
||||||
|
}
|
||||||
|
let mut pixels = {
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||||
|
Pixels::new(RES as u32, RES as u32, surface_texture)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut image_buffer = vec![0_u32; RES * RES];
|
event_loop.run(|event, elwt| {
|
||||||
let mut window = Window::new("SVC16", RES, RES, options)?;
|
let start_time = Instant::now();
|
||||||
window.set_target_fps(30);
|
if input.update(&event) {
|
||||||
window.set_cursor_visibility(cli.cursor);
|
if input.key_pressed(KeyCode::Escape) || input.close_requested() {
|
||||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
elwt.exit();
|
||||||
let mut ipf = 0_usize;
|
return;
|
||||||
let start = Instant::now();
|
|
||||||
while !engine.wants_to_sync() {
|
|
||||||
if let Some(debug_vals) = &cli.debug {
|
|
||||||
print_debug_info(debug_vals, &engine);
|
|
||||||
}
|
}
|
||||||
engine.step()?;
|
|
||||||
ipf += 1;
|
if let Some(size) = input.window_resized() {
|
||||||
if ipf >= cli.max_ipf {
|
if let Err(_) = pixels.resize_surface(size.width, size.height) {
|
||||||
break;
|
handle_event_loop_error(&elwt, "Resize error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut ipf = 0;
|
||||||
|
while !engine.wants_to_sync() && ipf <= cli.max_ipf {
|
||||||
|
match engine.step() {
|
||||||
|
Err(_) => {
|
||||||
|
handle_event_loop_error(&elwt, "Invalid operation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
ipf += 1;
|
||||||
|
}
|
||||||
|
let (c1, c2) = get_input_code(&input, &pixels);
|
||||||
|
let nb = engine.perform_sync(c1, c2);
|
||||||
|
update_image_buffer(pixels.frame_mut(), &nb);
|
||||||
|
|
||||||
|
let elapsed = start_time.elapsed();
|
||||||
|
if cli.verbose {
|
||||||
|
println!("Instructions: {} Frametime: {}ms", ipf, elapsed.as_millis());
|
||||||
|
}
|
||||||
|
if elapsed < FRAMETIME {
|
||||||
|
std::thread::sleep(FRAMETIME - elapsed);
|
||||||
|
}
|
||||||
|
window.request_redraw();
|
||||||
|
match pixels.render() {
|
||||||
|
Err(_) => {
|
||||||
|
handle_event_loop_error(&elwt, "Rendering error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let frametime = start.elapsed();
|
})?;
|
||||||
let input_code = get_input_code(&window);
|
|
||||||
let screenview = engine.perform_sync(input_code.0, input_code.1);
|
|
||||||
update_image_buffer(&mut image_buffer, &screenview);
|
|
||||||
window.update_with_buffer(&image_buffer, RES, RES)?;
|
|
||||||
if cli.verbose {
|
|
||||||
println!(
|
|
||||||
"frame needed {} instructions ({}ms)",
|
|
||||||
ipf,
|
|
||||||
frametime.as_millis()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_u16s_from_file(file_path: &str) -> Result<Vec<u16>> {
|
|
||||||
use std::io::{BufReader, Read};
|
|
||||||
let file = std::fs::File::open(file_path)?;
|
|
||||||
let mut reader = BufReader::new(file);
|
|
||||||
let mut buffer = [0u8; 2];
|
|
||||||
let mut u16s = Vec::new();
|
|
||||||
while reader.read_exact(&mut buffer).is_ok() {
|
|
||||||
let value = u16::from_le_bytes(buffer);
|
|
||||||
u16s.push(value);
|
|
||||||
}
|
|
||||||
Ok(u16s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rgb565_to_argb(rgb565: u16) -> u32 {
|
|
||||||
let r = ((rgb565 >> 11) & 0x1F) as u8;
|
|
||||||
let g = ((rgb565 >> 5) & 0x3F) as u8;
|
|
||||||
let b = (rgb565 & 0x1F) as u8;
|
|
||||||
let r = (r << 3) | (r >> 2);
|
|
||||||
let g = (g << 2) | (g >> 4);
|
|
||||||
let b = (b << 3) | (b >> 2);
|
|
||||||
(0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_image_buffer(imbuff: &mut Vec<u32>, screen: &[u16; RES * RES]) {
|
|
||||||
for i in 0..RES * RES {
|
|
||||||
*imbuff.get_mut(i).expect("Error with image buffer") = rgb565_to_argb(screen[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_input_code(window: &Window) -> (u16, u16) {
|
|
||||||
let mp = window.get_mouse_pos(minifb::MouseMode::Clamp).unwrap();
|
|
||||||
let pos_code = mp.1 as u16 * 256 + mp.0 as u16;
|
|
||||||
let mut key_code = 0_u16;
|
|
||||||
if window.get_mouse_down(minifb::MouseButton::Left) || window.is_key_down(Key::Space) {
|
|
||||||
key_code += 1;
|
|
||||||
}
|
|
||||||
if window.get_mouse_down(minifb::MouseButton::Right) || window.is_key_down(Key::B) {
|
|
||||||
key_code += 2;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::Up) || window.is_key_down(Key::W) {
|
|
||||||
key_code += 4;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::Down) || window.is_key_down(Key::S) {
|
|
||||||
key_code += 8;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::Left) || window.is_key_down(Key::A) {
|
|
||||||
key_code += 16;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::Right) || window.is_key_down(Key::D) {
|
|
||||||
key_code += 32;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::N) {
|
|
||||||
key_code += 64;
|
|
||||||
}
|
|
||||||
if window.is_key_down(Key::M) {
|
|
||||||
key_code += 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
(pos_code, key_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_debug_info(debug_vals: &Vec<u16>, engine: &Engine) {
|
|
||||||
let ptr = engine.get_instruction_pointer();
|
|
||||||
let inst = engine.read_instruction();
|
|
||||||
for d in debug_vals {
|
|
||||||
println!("@{}={}", d, engine.get(*d));
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"prt:{}, opcode:{}, args:[{},{},{}], @args:[{},{},{}]",
|
|
||||||
ptr,
|
|
||||||
inst[0],
|
|
||||||
inst[1],
|
|
||||||
inst[2],
|
|
||||||
inst[3],
|
|
||||||
engine.get(inst[1]),
|
|
||||||
engine.get(inst[2]),
|
|
||||||
engine.get(inst[3])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
82
src/utils.rs
Normal file
82
src/utils.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::RES;
|
||||||
|
use anyhow::Result;
|
||||||
|
use pixels::Pixels;
|
||||||
|
use winit::{
|
||||||
|
event::MouseButton,
|
||||||
|
event_loop::EventLoopWindowTarget,
|
||||||
|
keyboard::{Key, KeyCode},
|
||||||
|
};
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
|
pub fn read_u16s_from_file(file_path: &str) -> Result<Vec<u16>> {
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
|
let file = std::fs::File::open(file_path)?;
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
let mut buffer = [0u8; 2];
|
||||||
|
let mut u16s = Vec::new();
|
||||||
|
while reader.read_exact(&mut buffer).is_ok() {
|
||||||
|
let value = u16::from_le_bytes(buffer);
|
||||||
|
u16s.push(value);
|
||||||
|
}
|
||||||
|
Ok(u16s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rgb565_to_argb(rgb565: u16) -> (u8, u8, u8) {
|
||||||
|
let r = ((rgb565 >> 11) & 0x1F) as u8;
|
||||||
|
let g = ((rgb565 >> 5) & 0x3F) as u8;
|
||||||
|
let b = (rgb565 & 0x1F) as u8;
|
||||||
|
let r = (r << 3) | (r >> 2);
|
||||||
|
let g = (g << 2) | (g >> 4);
|
||||||
|
let b = (b << 3) | (b >> 2);
|
||||||
|
(r, g, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_image_buffer(imbuff: &mut [u8], screen: &[u16; RES * RES]) {
|
||||||
|
for i in 0..RES * RES {
|
||||||
|
let col = rgb565_to_argb(screen[i]);
|
||||||
|
*imbuff.get_mut(4 * i).expect("Error with image buffer") = col.0;
|
||||||
|
*imbuff.get_mut(4 * i + 1).expect("Error with image buffer") = col.1;
|
||||||
|
*imbuff.get_mut(4 * i + 2).expect("Error with image buffer") = col.2;
|
||||||
|
*imbuff.get_mut(4 * i + 3).expect("Error with image buffer") = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input_code(input: &WinitInputHelper, pxls: &Pixels) -> (u16, u16) {
|
||||||
|
let raw_mp = input.cursor().unwrap_or((0., 0.));
|
||||||
|
let mp = match pxls.window_pos_to_pixel(raw_mp) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(ev) => pxls.clamp_pixel_pos(ev),
|
||||||
|
};
|
||||||
|
let pos_code = (mp.1 as u16 * 256) + mp.0 as u16;
|
||||||
|
let mut key_code = 0_u16;
|
||||||
|
if input.key_held(KeyCode::Space) || input.mouse_held(MouseButton::Left) {
|
||||||
|
key_code += 1;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("b")) || input.mouse_held(MouseButton::Right) {
|
||||||
|
key_code += 2;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("w")) || input.key_held(KeyCode::ArrowUp) {
|
||||||
|
key_code += 4;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("s")) || input.key_held(KeyCode::ArrowDown) {
|
||||||
|
key_code += 8;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("a")) || input.key_held(KeyCode::ArrowLeft) {
|
||||||
|
key_code += 16;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("d")) || input.key_held(KeyCode::ArrowRight) {
|
||||||
|
key_code += 32;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("n")) {
|
||||||
|
key_code += 64;
|
||||||
|
}
|
||||||
|
if input.key_held_logical(Key::Character("m")) {
|
||||||
|
key_code += 128;
|
||||||
|
}
|
||||||
|
(pos_code, key_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event_loop_error(handle: &EventLoopWindowTarget<()>, msg: impl AsRef<str>) {
|
||||||
|
eprintln!("{}", msg.as_ref());
|
||||||
|
handle.exit();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user