diff --git a/Cargo.lock b/Cargo.lock index fa4b4fc..7e09314 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3171,6 +3171,8 @@ dependencies = [ "anyhow", "cpal", "curlywas", + "env_logger 0.9.0", + "log", "notify", "pico-args", "rubato", @@ -3207,6 +3209,7 @@ dependencies = [ "env_logger 0.9.0", "log", "minifb", + "pico-args", "pollster", "wgpu", "winapi 0.3.9", diff --git a/Cargo.toml b/Cargo.toml index 4fe2c03..5addf32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ browser = ["warp", "tokio", "tokio-stream", "webbrowser"] [dependencies] wasmtime = { version = "0.37.0", optional = true } anyhow = "1" +env_logger = "0.9" +log = "0.4" uw8-window = { path = "uw8-window", optional = true } notify = "4" pico-args = "0.4" diff --git a/README.md b/README.md index 0989a30..39e839a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,27 @@ Options: -l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow. -o FILE, --output FILE : Write the loaded and optionally packed cart back to disk. +when using the native runtime: + +-m, --no-audio : Disable audio, also reduces cpu load a bit +--no-gpu : Force old cpu-only window code +--filter FILTER : Select an upscale filter at startup +--fullscreen : Start in fullscreen mode + +Note that the cpu-only window does not support fullscreen nor upscale filters. + +Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails. +Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter, +you can just pass "--filter nearest" or "--filter 1". + +The upscale filter options are: +1, nearest : Anti-aliased nearest filter +2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720 +3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes +4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap +5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise + +You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F. uw8 pack [] diff --git a/site/content/docs.md b/site/content/docs.md index d69a34f..c26c2d8 100644 --- a/site/content/docs.md +++ b/site/content/docs.md @@ -436,6 +436,30 @@ and execution of the cart is stopped. Defaults to 30 (0.5s) * `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow. * `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk. +when using the native runtime: + +* `-m`, `--no-audio`: Disable audio, also reduces cpu load a bit +* `--no-gpu`: Force old cpu-only window code +* `--filter FILTER`: Select an upscale filter at startup +* `--fullscreen`: Start in fullscreen mode + +Note that the cpu-only window does not support fullscreen nor upscale filters. + +Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails. +Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter, +you can just pass `--filter nearest` or `--filter 1`. + +The upscale filter options are: +``` +1, nearest : Anti-aliased nearest filter +2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720 +3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes +4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap +5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise +``` + +You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F. + ## `uw8 pack` Usage: diff --git a/src/main.rs b/src/main.rs index 93e0c03..f1fc3c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use uw8::RunWebServer; use uw8::Runtime; fn main() -> Result<()> { + env_logger::Builder::from_env(env_logger::Env::default()).init(); let mut args = Arguments::from_env(); // try to enable ansi support in win10 cmd shell @@ -35,7 +36,7 @@ fn main() -> Result<()> { println!(); println!("Usage:"); #[cfg(any(feature = "native", feature = "browser"))] - println!(" uw8 run [-t/--timeout ] [--no-gpu] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); + println!(" uw8 run [-t/--timeout ] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); println!(" uw8 pack [-u/--uncompressed] [-l/--level] "); println!(" uw8 unpack "); println!(" uw8 compile [-d/--debug] "); @@ -54,8 +55,6 @@ fn run(mut args: Arguments) -> Result<()> { let watch_mode = args.contains(["-w", "--watch"]); #[allow(unused)] let timeout: Option = args.opt_value_from_str(["-t", "--timeout"])?; - #[allow(unused)] - let gpu = !args.contains("--no-gpu"); let mut config = Config::default(); if args.contains(["-p", "--pack"]) { @@ -82,7 +81,16 @@ fn run(mut args: Arguments) -> Result<()> { #[cfg(not(feature = "native"))] let run_browser = args.contains(["-b", "--browser"]) || true; - let disable_audio = args.contains(["-m", "--disable-audio"]); + let disable_audio = args.contains(["-m", "--no-audio"]); + + #[cfg(feature = "native")] + let window_config = { + let mut config = WindowConfig::default(); + if !run_browser { + config.parse_arguments(&mut args); + } + config + }; let filename = args.free_from_os_str::(|s| Ok(s.into()))?; @@ -90,12 +98,14 @@ fn run(mut args: Arguments) -> Result<()> { use std::process::exit; + use uw8_window::WindowConfig; + let mut runtime: Box = if !run_browser { #[cfg(not(feature = "native"))] unimplemented!(); #[cfg(feature = "native")] { - let mut microw8 = MicroW8::new(timeout, gpu)?; + let mut microw8 = MicroW8::new(timeout, window_config)?; if disable_audio { microw8.disable_audio(); } diff --git a/src/run_native.rs b/src/run_native.rs index c15a322..1e3ee41 100644 --- a/src/run_native.rs +++ b/src/run_native.rs @@ -5,7 +5,7 @@ use std::{thread, time::Instant}; use anyhow::{anyhow, Result}; use cpal::traits::*; use rubato::Resampler; -use uw8_window::Window; +use uw8_window::{Window, WindowConfig}; use wasmtime::{ Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, }; @@ -45,7 +45,7 @@ struct UW8WatchDog { } impl MicroW8 { - pub fn new(timeout: Option, gpu: bool) -> Result { + pub fn new(timeout: Option, window_config: WindowConfig) -> Result { let mut config = wasmtime::Config::new(); config.cranelift_opt_level(wasmtime::OptLevel::Speed); if timeout.is_some() { @@ -56,7 +56,7 @@ impl MicroW8 { let loader_module = wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; - let window = Window::new(gpu)?; + let window = Window::new(window_config)?; Ok(MicroW8 { window, diff --git a/uw8-window/Cargo.lock b/uw8-window/Cargo.lock index e944fb7..34ba9e1 100644 --- a/uw8-window/Cargo.lock +++ b/uw8-window/Cargo.lock @@ -986,6 +986,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1296,6 +1302,7 @@ dependencies = [ "env_logger", "log", "minifb", + "pico-args", "pollster", "wgpu", "winapi", diff --git a/uw8-window/Cargo.toml b/uw8-window/Cargo.toml index 11eff51..8ef2236 100644 --- a/uw8-window/Cargo.toml +++ b/uw8-window/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" winit = "0.26.1" env_logger = "0.9" log = "0.4" +pico-args = "0.4" wgpu = "0.13.1" pollster = "0.2" bytemuck = { version = "1.4", features = [ "derive" ] } diff --git a/uw8-window/src/cpu.rs b/uw8-window/src/cpu.rs index 02081b3..1e026e3 100644 --- a/uw8-window/src/cpu.rs +++ b/uw8-window/src/cpu.rs @@ -78,6 +78,6 @@ impl WindowImpl for Window { } fn is_open(&self) -> bool { - self.window.is_open() + self.window.is_open() && !self.window.is_key_down(Key::Escape) } } diff --git a/uw8-window/src/gpu/mod.rs b/uw8-window/src/gpu/mod.rs index 407eab5..f8811e5 100644 --- a/uw8-window/src/gpu/mod.rs +++ b/uw8-window/src/gpu/mod.rs @@ -1,4 +1,4 @@ -use crate::{Input, WindowImpl}; +use crate::{Input, WindowConfig, WindowImpl}; use anyhow::{anyhow, Result}; use std::{num::NonZeroU32, time::Instant}; @@ -37,13 +37,18 @@ pub struct Window { } impl Window { - pub fn new() -> Result { - async fn create() -> Result { + pub fn new(window_config: WindowConfig) -> Result { + async fn create(window_config: WindowConfig) -> Result { let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_inner_size(PhysicalSize::new(640u32, 480)) .with_min_inner_size(PhysicalSize::new(320u32, 240)) .with_title("MicroW8") + .with_fullscreen(if window_config.fullscreen { + Some(Fullscreen::Borderless(None)) + } else { + None + }) .build(&event_loop)?; window.set_cursor_visible(false); @@ -73,12 +78,13 @@ impl Window { present_mode: wgpu::PresentMode::AutoNoVsync, }; - let filter: Box = Box::new(AutoCrtFilter::new( + let filter: Box = create_filter( &device, &palette_screen_mode.screen_view, window.inner_size(), surface_config.format, - )); + window_config.filter, + ); surface.configure(&device, &surface_config); @@ -95,12 +101,12 @@ impl Window { filter, gamepads: [0; 4], next_frame: Instant::now(), - is_fullscreen: false, + is_fullscreen: window_config.fullscreen, is_open: true, }) } - pollster::block_on(create()) + pollster::block_on(create(window_config)) } } @@ -109,6 +115,7 @@ impl WindowImpl for Window { let mut reset = false; self.event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::WaitUntil(self.next_frame); + let mut new_filter = None; match event { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(new_size) => { @@ -153,48 +160,11 @@ impl WindowImpl for Window { self.window.set_fullscreen(fullscreen); } Some(VirtualKeyCode::R) => reset = true, - Some(VirtualKeyCode::Key1) => { - self.filter = Box::new(SquareFilter::new( - &self.device, - &self.palette_screen_mode.screen_view, - self.window.inner_size(), - self.surface_config.format, - )) - } - Some(VirtualKeyCode::Key2) => { - self.filter = Box::new(FastCrtFilter::new( - &self.device, - &self.palette_screen_mode.screen_view, - self.window.inner_size(), - self.surface_config.format, - false, - )) - } - Some(VirtualKeyCode::Key3) => { - self.filter = Box::new(CrtFilter::new( - &self.device, - &self.palette_screen_mode.screen_view, - self.window.inner_size(), - self.surface_config.format, - )) - } - Some(VirtualKeyCode::Key4) => { - self.filter = Box::new(FastCrtFilter::new( - &self.device, - &self.palette_screen_mode.screen_view, - self.window.inner_size(), - self.surface_config.format, - true, - )) - } - Some(VirtualKeyCode::Key5) => { - self.filter = Box::new(AutoCrtFilter::new( - &self.device, - &self.palette_screen_mode.screen_view, - self.window.inner_size(), - self.surface_config.format, - )) - } + Some(VirtualKeyCode::Key1) => new_filter = Some(1), + Some(VirtualKeyCode::Key2) => new_filter = Some(2), + Some(VirtualKeyCode::Key3) => new_filter = Some(3), + Some(VirtualKeyCode::Key4) => new_filter = Some(4), + Some(VirtualKeyCode::Key5) => new_filter = Some(5), _ => (), } @@ -215,6 +185,15 @@ impl WindowImpl for Window { } _ => (), } + if let Some(new_filter) = new_filter { + self.filter = create_filter( + &self.device, + &self.palette_screen_mode.screen_view, + self.window.inner_size(), + self.surface_config.format, + new_filter, + ); + } }); Input { gamepads: self.gamepads, @@ -269,6 +248,49 @@ impl WindowImpl for Window { } } +fn create_filter( + device: &wgpu::Device, + screen_texture: &wgpu::TextureView, + window_size: PhysicalSize, + surface_format: wgpu::TextureFormat, + filter: u32, +) -> Box { + match filter { + 1 => Box::new(SquareFilter::new( + device, + screen_texture, + window_size, + surface_format, + )), + 2 => Box::new(FastCrtFilter::new( + device, + screen_texture, + window_size, + surface_format, + false, + )), + 3 => Box::new(CrtFilter::new( + device, + screen_texture, + window_size, + surface_format, + )), + 4 => Box::new(FastCrtFilter::new( + device, + screen_texture, + window_size, + surface_format, + true, + )), + _ => Box::new(AutoCrtFilter::new( + device, + screen_texture, + window_size, + surface_format, + )), + } +} + trait Filter { fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize); fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>); diff --git a/uw8-window/src/lib.rs b/uw8-window/src/lib.rs index 328f84a..dd2dd27 100644 --- a/uw8-window/src/lib.rs +++ b/uw8-window/src/lib.rs @@ -7,9 +7,9 @@ mod gpu; pub struct Window(Box); impl Window { - pub fn new(gpu: bool) -> Result { - if gpu { - match gpu::Window::new() { + pub fn new(config: WindowConfig) -> Result { + if config.enable_gpu { + match gpu::Window::new(config) { Ok(window) => return Ok(Window(Box::new(window))), Err(err) => eprintln!( "Failed to create gpu window: {}\nFalling back tp cpu window", @@ -32,6 +32,43 @@ impl Window { } } +#[derive(Debug)] +pub struct WindowConfig { + enable_gpu: bool, + filter: u32, + fullscreen: bool, +} + +impl Default for WindowConfig { + fn default() -> WindowConfig { + WindowConfig { + enable_gpu: true, + filter: 5, + fullscreen: false, + } + } +} + +impl WindowConfig { + pub fn parse_arguments(&mut self, args: &mut pico_args::Arguments) { + self.enable_gpu = !args.contains("--no-gpu"); + if let Some(filter) = args.opt_value_from_str::<_, String>("--filter").unwrap() { + self.filter = match filter.as_str() { + "1" | "nearest" => 1, + "2" | "fast_crt" => 2, + "3" | "ss_crt" => 3, + "4" | "chromatic" => 4, + "5" | "auto_crt" => 5, + o => { + println!("Unknown --filter '{}'", o); + std::process::exit(1); + } + } + } + self.fullscreen = args.contains("--fullscreen"); + } +} + pub struct Input { pub gamepads: [u8; 4], pub reset: bool, diff --git a/uw8-window/src/main.rs b/uw8-window/src/main.rs index 15a155c..d7bf52a 100644 --- a/uw8-window/src/main.rs +++ b/uw8-window/src/main.rs @@ -1,8 +1,11 @@ use std::time::Instant; +use uw8_window::WindowConfig; fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let mut args = pico_args::Arguments::from_env(); + let mut framebuffer = vec![0u8; 320 * 240]; let mut start_time = Instant::now(); @@ -18,7 +21,10 @@ fn main() { let mut fps_start = Instant::now(); let mut fps_counter = 0; - let mut window = uw8_window::Window::new(true).unwrap(); + let mut window_config = WindowConfig::default(); + window_config.parse_arguments(&mut args); + + let mut window = uw8_window::Window::new(window_config).unwrap(); while window.is_open() { let input = window.begin_frame();