From 499bb02f2ccd1c6ae8c111bae74d266ef0047d29 Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Thu, 21 Jul 2022 08:51:17 +0200 Subject: [PATCH] restructure control flow of uw8-window to hopefully make it work on MacOS --- Cargo.lock | 2 +- src/run_native.rs | 95 ++++---------- uw8-window/src/cpu.rs | 86 ++++++------ uw8-window/src/gpu/mod.rs | 268 +++++++++++++++++++------------------- uw8-window/src/lib.rs | 48 +++++-- uw8-window/src/main.rs | 18 ++- 6 files changed, 250 insertions(+), 267 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8fbec2..fa4b4fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3165,7 +3165,7 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uw8" -version = "0.2.0" +version = "0.2.1" dependencies = [ "ansi_term", "anyhow", diff --git a/src/run_native.rs b/src/run_native.rs index 190c3b7..c15a322 100644 --- a/src/run_native.rs +++ b/src/run_native.rs @@ -5,23 +5,20 @@ use std::{thread, time::Instant}; use anyhow::{anyhow, Result}; use cpal::traits::*; use rubato::Resampler; +use uw8_window::Window; use wasmtime::{ Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, }; pub struct MicroW8 { - tx: mpsc::SyncSender>, - rx: mpsc::Receiver, + window: Window, stream: Option, engine: Engine, loader_module: Module, disable_audio: bool, module_data: Option>, -} - -enum UIEvent { - Error(Result<()>), - Reset, + timeout: u32, + instance: Option, } struct UW8Instance { @@ -59,39 +56,17 @@ impl MicroW8 { let loader_module = wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; - let (to_ui_tx, to_ui_rx) = mpsc::sync_channel(2); - let (from_ui_tx, from_ui_rx) = mpsc::sync_channel(1); - - std::thread::spawn(move || { - let mut state = State { - instance: None, - timeout: timeout.unwrap_or(0), - }; - - uw8_window::run(gpu, move |framebuffer, gamepad, reset| { - while let Ok(instance) = to_ui_rx.try_recv() { - state.instance = instance; - } - - if reset { - from_ui_tx.send(UIEvent::Reset).unwrap(); - } - - state.run_frame(framebuffer, gamepad).unwrap_or_else(|err| { - from_ui_tx.send(UIEvent::Error(Err(err))).unwrap(); - Instant::now() - }) - }); - }); + let window = Window::new(gpu)?; Ok(MicroW8 { - tx: to_ui_tx, - rx: from_ui_rx, + window, stream: None, engine, loader_module, disable_audio: false, module_data: None, + timeout: timeout.unwrap_or(0), + instance: None, }) } @@ -102,12 +77,12 @@ impl MicroW8 { impl super::Runtime for MicroW8 { fn is_open(&self) -> bool { - true + self.window.is_open() } fn load(&mut self, module_data: &[u8]) -> Result<()> { self.stream = None; - self.tx.send(None)?; + self.instance = None; let mut store = wasmtime::Store::new(&self.engine, ()); store.set_epoch_deadline(60); @@ -174,7 +149,7 @@ impl super::Runtime for MicroW8 { } }; - self.tx.send(Some(UW8Instance { + self.instance = Some(UW8Instance { store, memory, end_frame, @@ -182,56 +157,36 @@ impl super::Runtime for MicroW8 { start_time: Instant::now(), watchdog, sound_tx, - }))?; + }); self.stream = stream; self.module_data = Some(module_data.into()); Ok(()) } fn run_frame(&mut self) -> Result<()> { - if let Ok(event) = self.rx.try_recv() { - match event { - UIEvent::Error(err) => err, - UIEvent::Reset => { - if let Some(module_data) = self.module_data.take() { - self.load(&module_data) - } else { - Ok(()) - } - } + let input = self.window.begin_frame(); + + if input.reset { + if let Some(module_data) = self.module_data.take() { + self.load(&module_data)?; } - } else { - Ok(()) } - } -} -struct State { - instance: Option, - timeout: u32, -} - -impl State { - fn run_frame( - &mut self, - framebuffer: &mut dyn uw8_window::Framebuffer, - gamepad: u32, - ) -> Result { let now = Instant::now(); - let mut result = Ok(now); + let mut result = Ok(()); if let Some(mut instance) = self.instance.take() { let time = (now - instance.start_time).as_millis() as i32; - { + let next_frame = { let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6; let max = now + Duration::from_millis(17); let next_center = now + Duration::from_millis((16 - offset) as u64); - result = Ok(next_center.min(max)); - } + next_center.min(max) + }; { let mem = instance.memory.data_mut(&mut instance.store); mem[64..68].copy_from_slice(&time.to_le_bytes()); - mem[68..72].copy_from_slice(&gamepad.to_le_bytes()); + mem[68..72].copy_from_slice(&input.gamepads); } instance.store.set_epoch_deadline(self.timeout as u64); @@ -255,14 +210,16 @@ impl State { let framebuffer_mem = &memory[120..(120 + 320 * 240)]; let palette_mem = &memory[0x13000..]; - framebuffer.update(framebuffer_mem, palette_mem); + self.window + .end_frame(framebuffer_mem, palette_mem, next_frame); if result.is_ok() { self.instance = Some(instance); } } - Ok(result?) + result?; + Ok(()) } } diff --git a/uw8-window/src/cpu.rs b/uw8-window/src/cpu.rs index 044a91f..02081b3 100644 --- a/uw8-window/src/cpu.rs +++ b/uw8-window/src/cpu.rs @@ -1,7 +1,8 @@ use std::time::Instant; -use crate::Framebuffer; -use minifb::{Key, Window, WindowOptions}; +use crate::{Input, WindowImpl}; +use anyhow::Result; +use minifb::{Key, WindowOptions}; static GAMEPAD_KEYS: &[Key] = &[ Key::Up, @@ -14,58 +15,53 @@ static GAMEPAD_KEYS: &[Key] = &[ Key::S, ]; -pub fn run(mut update: Box Instant + 'static>) -> ! { - #[cfg(target_os = "windows")] - unsafe { - winapi::um::timeapi::timeBeginPeriod(1); - } +pub struct Window { + window: minifb::Window, + buffer: Vec, +} - let mut buffer: Vec = vec![0; 320 * 240]; - - let options = WindowOptions { - scale: minifb::Scale::X2, - scale_mode: minifb::ScaleMode::AspectRatioStretch, - resize: true, - ..Default::default() - }; - let mut window = Window::new("MicroW8", 320, 240, options).unwrap(); - - let mut next_frame = Instant::now(); - while window.is_open() && !window.is_key_down(Key::Escape) { - if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) { - std::thread::sleep(sleep); +impl Window { + pub fn new() -> Result { + #[cfg(target_os = "windows")] + unsafe { + winapi::um::timeapi::timeBeginPeriod(1); } - let mut gamepad = 0; - for key in window.get_keys() { + let buffer: Vec = vec![0; 320 * 240]; + + let options = WindowOptions { + scale: minifb::Scale::X2, + scale_mode: minifb::ScaleMode::AspectRatioStretch, + resize: true, + ..Default::default() + }; + let window = minifb::Window::new("MicroW8", 320, 240, options).unwrap(); + + Ok(Window { window, buffer }) + } +} + +impl WindowImpl for Window { + fn begin_frame(&mut self) -> Input { + let mut gamepads = [0u8; 4]; + for key in self.window.get_keys() { if let Some(index) = GAMEPAD_KEYS .iter() .enumerate() .find(|(_, &k)| k == key) .map(|(i, _)| i) { - gamepad |= 1 << index; + gamepads[0] |= 1 << index; } } - next_frame = update( - &mut CpuFramebuffer { - buffer: &mut buffer, - }, - gamepad, - window.is_key_pressed(Key::R, minifb::KeyRepeat::No), - ); - window.update_with_buffer(&buffer, 320, 240).unwrap(); + Input { + gamepads, + reset: self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No), + } } - std::process::exit(0); -} -struct CpuFramebuffer<'a> { - buffer: &'a mut Vec, -} - -impl<'a> Framebuffer for CpuFramebuffer<'a> { - fn update(&mut self, framebuffer: &[u8], palette: &[u8]) { + fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) { for (i, &color_index) in framebuffer.iter().enumerate() { let offset = color_index as usize * 4; self.buffer[i] = 0xff000000 @@ -73,5 +69,15 @@ impl<'a> Framebuffer for CpuFramebuffer<'a> { | ((palette[offset + 1] as u32) << 8) | palette[offset + 2] as u32; } + self.window + .update_with_buffer(&self.buffer, 320, 240) + .unwrap(); + if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) { + std::thread::sleep(sleep); + } + } + + fn is_open(&self) -> bool { + self.window.is_open() } } diff --git a/uw8-window/src/gpu/mod.rs b/uw8-window/src/gpu/mod.rs index 61d5b87..be1a15a 100644 --- a/uw8-window/src/gpu/mod.rs +++ b/uw8-window/src/gpu/mod.rs @@ -1,4 +1,4 @@ -use crate::Framebuffer; +use crate::{Input, WindowImpl}; use anyhow::{anyhow, Result}; use std::{num::NonZeroU32, time::Instant}; @@ -9,12 +9,7 @@ use winit::{ window::{Fullscreen, WindowBuilder}, }; -#[cfg(target_os = "macos")] -use winit::platform::macos::EventLoopExtMacOS; -#[cfg(target_os = "linux")] -use winit::platform::unix::EventLoopExtUnix; -#[cfg(target_os = "windows")] -use winit::platform::windows::EventLoopExtWindows; +use winit::platform::run_return::EventLoopExtRunReturn; mod crt; mod fast_crt; @@ -25,19 +20,26 @@ use fast_crt::FastCrtFilter; use square::SquareFilter; pub struct Window { - event_loop: EventLoop<()>, - window: winit::window::Window, - instance: wgpu::Instance, + _instance: wgpu::Instance, surface: wgpu::Surface, - adapter: wgpu::Adapter, + _adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, + palette_screen_mode: PaletteScreenMode, + surface_config: wgpu::SurfaceConfiguration, + filter: Box, + event_loop: EventLoop<()>, + window: winit::window::Window, + gamepads: [u8; 4], + next_frame: Instant, + is_fullscreen: bool, + is_open: bool, } impl Window { pub fn new() -> Result { async fn create() -> Result { - let event_loop = EventLoop::new_any_thread(); + let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_inner_size(PhysicalSize::new(640u32, 480)) .with_min_inner_size(PhysicalSize::new(320u32, 240)) @@ -61,70 +63,66 @@ impl Window { .request_device(&wgpu::DeviceDescriptor::default(), None) .await?; + let palette_screen_mode = PaletteScreenMode::new(&device); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface.get_supported_formats(&adapter)[0], + width: window.inner_size().width, + height: window.inner_size().height, + present_mode: wgpu::PresentMode::AutoNoVsync, + }; + + let filter: Box = Box::new(CrtFilter::new( + &device, + &palette_screen_mode.screen_view, + window.inner_size(), + surface_config.format, + )); + + surface.configure(&device, &surface_config); + Ok(Window { event_loop, window, - instance, + _instance: instance, surface, - adapter, + _adapter: adapter, device, queue, + palette_screen_mode, + surface_config, + filter, + gamepads: [0; 4], + next_frame: Instant::now(), + is_fullscreen: false, + is_open: true, }) } pollster::block_on(create()) } +} - pub fn run( - self, - mut update: Box Instant + 'static>, - ) -> ! { - let Window { - event_loop, - window, - instance, - surface, - adapter, - device, - queue, - } = self; - - let palette_screen_mode = PaletteScreenMode::new(&device); - - let mut surface_config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface.get_supported_formats(&adapter)[0], - width: window.inner_size().width, - height: window.inner_size().height, - present_mode: wgpu::PresentMode::AutoNoVsync, - }; - - let mut filter: Box = Box::new(CrtFilter::new( - &device, - &palette_screen_mode.screen_view, - window.inner_size(), - surface_config.format, - )); - - surface.configure(&device, &surface_config); - +impl WindowImpl for Window { + fn begin_frame(&mut self) -> Input { let mut reset = false; - let mut gamepad = 0; - - event_loop.run(move |event, _, control_flow| { - let _ = (&window, &instance, &surface, &adapter, &device); - + self.event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::WaitUntil(self.next_frame); match event { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(new_size) => { - surface_config.width = new_size.width; - surface_config.height = new_size.height; - surface.configure(&device, &surface_config); - filter.resize(&queue, new_size); + self.surface_config.width = new_size.width; + self.surface_config.height = new_size.height; + self.surface.configure(&self.device, &self.surface_config); + self.filter.resize(&self.queue, new_size); + } + WindowEvent::CloseRequested => { + self.is_open = false; + *control_flow = ControlFlow::Exit; } - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { input, .. } => { - fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 { + fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 { match input.scancode { 44 => 16, 45 => 32, @@ -141,116 +139,114 @@ impl Window { } if input.state == winit::event::ElementState::Pressed { match input.virtual_keycode { - Some(VirtualKeyCode::Escape) => *control_flow = ControlFlow::Exit, + Some(VirtualKeyCode::Escape) => { + self.is_open = false; + *control_flow = ControlFlow::Exit; + } Some(VirtualKeyCode::F) => { - window.set_fullscreen(if window.fullscreen().is_some() { + let fullscreen = if self.window.fullscreen().is_some() { None } else { Some(Fullscreen::Borderless(None)) - }); + }; + self.is_fullscreen = fullscreen.is_some(); + self.window.set_fullscreen(fullscreen); } Some(VirtualKeyCode::R) => reset = true, Some(VirtualKeyCode::Key1) => { - filter = Box::new(SquareFilter::new( - &device, - &palette_screen_mode.screen_view, - window.inner_size(), - surface_config.format, + 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) => { - filter = Box::new(FastCrtFilter::new( - &device, - &palette_screen_mode.screen_view, - window.inner_size(), - surface_config.format, + self.filter = Box::new(FastCrtFilter::new( + &self.device, + &self.palette_screen_mode.screen_view, + self.window.inner_size(), + self.surface_config.format, )) } Some(VirtualKeyCode::Key3) => { - filter = Box::new(CrtFilter::new( - &device, - &palette_screen_mode.screen_view, - window.inner_size(), - surface_config.format, + self.filter = Box::new(CrtFilter::new( + &self.device, + &self.palette_screen_mode.screen_view, + self.window.inner_size(), + self.surface_config.format, )) } _ => (), } - gamepad |= gamepad_button(&input); + self.gamepads[0] |= gamepad_button(&input); } else { - gamepad &= !gamepad_button(&input); + self.gamepads[1] &= !gamepad_button(&input); } } _ => (), }, - Event::MainEventsCleared => { - if let ControlFlow::WaitUntil(t) = *control_flow { - if Instant::now() < t { - return; - } - } - let next_frame = update( - &mut GpuFramebuffer { - queue: &queue, - framebuffer: &palette_screen_mode, - }, - gamepad, - reset, - ); - reset = false; - *control_flow = ControlFlow::WaitUntil(next_frame); - - let output = surface.get_current_texture().unwrap(); - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - palette_screen_mode.resolve_screen(&mut encoder); - + Event::RedrawEventsCleared => { + if Instant::now() >= self.next_frame + && self.window.fullscreen().is_some() == self.is_fullscreen { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - filter.render(&mut render_pass); + *control_flow = ControlFlow::Exit } - - queue.submit(std::iter::once(encoder.finish())); - output.present(); } _ => (), } }); + Input { + gamepads: self.gamepads, + reset, + } } -} -struct GpuFramebuffer<'a> { - framebuffer: &'a PaletteScreenMode, - queue: &'a wgpu::Queue, -} + fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) { + self.next_frame = next_frame; + self.palette_screen_mode + .write_framebuffer(&self.queue, framebuffer); + self.palette_screen_mode.write_palette(&self.queue, palette); -impl<'a> Framebuffer for GpuFramebuffer<'a> { - fn update(&mut self, pixels: &[u8], palette: &[u8]) { - self.framebuffer.write_framebuffer(self.queue, pixels); - self.framebuffer.write_palette(self.queue, palette); + let output = self.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + self.palette_screen_mode.resolve_screen(&mut encoder); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + self.filter.render(&mut render_pass); + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + } + + fn is_open(&self) -> bool { + self.is_open } } diff --git a/uw8-window/src/lib.rs b/uw8-window/src/lib.rs index cc113a5..328f84a 100644 --- a/uw8-window/src/lib.rs +++ b/uw8-window/src/lib.rs @@ -1,24 +1,44 @@ +use anyhow::Result; use std::time::Instant; mod cpu; mod gpu; -pub fn run Instant>( - gpu: bool, - update: F, -) -> ! { - if gpu { - match gpu::Window::new() { - Ok(window) => window.run(Box::new(update)), - Err(err) => eprintln!( - "Failed to create gpu window: {}\nFalling back to cpu window", - err - ), +pub struct Window(Box); + +impl Window { + pub fn new(gpu: bool) -> Result { + if gpu { + match gpu::Window::new() { + Ok(window) => return Ok(Window(Box::new(window))), + Err(err) => eprintln!( + "Failed to create gpu window: {}\nFalling back tp cpu window", + err + ), + } } + cpu::Window::new().map(|window| Window(Box::new(window))) + } + + pub fn begin_frame(&mut self) -> Input { + self.0.begin_frame() + } + pub fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) { + self.0.end_frame(framebuffer, palette, next_frame) + } + + pub fn is_open(&self) -> bool { + self.0.is_open() } - cpu::run(Box::new(update)); } -pub trait Framebuffer { - fn update(&mut self, pixels: &[u8], palette: &[u8]); +pub struct Input { + pub gamepads: [u8; 4], + pub reset: bool, +} + +trait WindowImpl { + fn begin_frame(&mut self) -> Input; + fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant); + fn is_open(&self) -> bool; } diff --git a/uw8-window/src/main.rs b/uw8-window/src/main.rs index 1bcf986..15a155c 100644 --- a/uw8-window/src/main.rs +++ b/uw8-window/src/main.rs @@ -4,7 +4,7 @@ fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let mut framebuffer = vec![0u8; 320 * 240]; - let start_time = Instant::now(); + let mut start_time = Instant::now(); let mut palette = vec![0u32; 256]; for i in 0..256 { @@ -18,11 +18,16 @@ fn main() { let mut fps_start = Instant::now(); let mut fps_counter = 0; - uw8_window::run(true, move |gpu_framebuffer, _gamepads, _reset| { - for _ in 0..1 { - draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32()); + let mut window = uw8_window::Window::new(true).unwrap(); + + while window.is_open() { + let input = window.begin_frame(); + if input.reset { + start_time = Instant::now(); } - gpu_framebuffer.update(&framebuffer, bytemuck::cast_slice(&palette)); + draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32()); + window.end_frame(&framebuffer, bytemuck::cast_slice(&palette), Instant::now()); + fps_counter += 1; let elapsed = fps_start.elapsed().as_secs_f32(); if elapsed >= 1.0 { @@ -30,8 +35,7 @@ fn main() { fps_start = Instant::now(); fps_counter = 0; } - Instant::now() - }); + } } fn draw_frame(framebuffer: &mut [u8], time: f32) {