first somewhat working version of uw8 using gpu window

This commit is contained in:
2022-07-08 23:29:39 +02:00
parent b0adf7748d
commit 9dabf75732
5 changed files with 927 additions and 248 deletions

994
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,13 +7,13 @@ edition = "2021"
[features] [features]
default = ["native", "browser"] default = ["native", "browser"]
native = ["wasmtime", "minifb", "cpal", "rubato", "winapi" ] native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"] browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies] [dependencies]
wasmtime = { version = "0.37.0", optional = true } wasmtime = { version = "0.37.0", optional = true }
anyhow = "1" anyhow = "1"
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true } uw8-window = { path = "uw8-window", optional = true }
notify = "4" notify = "4"
pico-args = "0.4" pico-args = "0.4"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" } curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
@@ -27,4 +27,3 @@ webbrowser = { version = "0.6.0", optional = true }
ansi_term = "0.12.1" ansi_term = "0.12.1"
cpal = { version = "0.13.5", optional = true } cpal = { version = "0.13.5", optional = true }
rubato = { version = "0.11.0", optional = true } rubato = { version = "0.11.0", optional = true }
winapi = { version = "0.3.9", features = ["timeapi"], optional = true }

View File

@@ -4,31 +4,13 @@ use std::{thread, time::Instant};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use cpal::traits::*; use cpal::traits::*;
use minifb::{Key, Window, WindowOptions};
use rubato::Resampler; use rubato::Resampler;
use wasmtime::{ use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
}; };
static GAMEPAD_KEYS: &[Key] = &[
Key::Up,
Key::Down,
Key::Left,
Key::Right,
Key::Z,
Key::X,
Key::A,
Key::S,
];
pub struct MicroW8 { pub struct MicroW8 {
engine: Engine, tx: mpsc::SyncSender<Vec<u8>>,
loader_module: Module,
window: Window,
window_buffer: Vec<u32>,
instance: Option<UW8Instance>,
timeout: u32,
disable_audio: bool,
} }
struct UW8Instance { struct UW8Instance {
@@ -37,7 +19,6 @@ struct UW8Instance {
end_frame: TypedFunc<(), ()>, end_frame: TypedFunc<(), ()>,
update: Option<TypedFunc<(), ()>>, update: Option<TypedFunc<(), ()>>,
start_time: Instant, start_time: Instant,
next_frame: Instant,
module: Vec<u8>, module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>, watchdog: Arc<Mutex<UW8WatchDog>>,
sound: Option<Uw8Sound>, sound: Option<Uw8Sound>,
@@ -58,11 +39,6 @@ struct UW8WatchDog {
impl MicroW8 { impl MicroW8 {
pub fn new(timeout: Option<u32>) -> Result<MicroW8> { pub fn new(timeout: Option<u32>) -> Result<MicroW8> {
#[cfg(target_os = "windows")]
unsafe {
winapi::um::timeapi::timeBeginPeriod(1);
}
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
config.cranelift_opt_level(wasmtime::OptLevel::Speed); config.cranelift_opt_level(wasmtime::OptLevel::Speed);
if timeout.is_some() { if timeout.is_some() {
@@ -73,44 +49,65 @@ impl MicroW8 {
let loader_module = let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let options = WindowOptions { let (tx, rx) = mpsc::sync_channel::<Vec<u8>>(1);
scale: minifb::Scale::X2,
scale_mode: minifb::ScaleMode::AspectRatioStretch,
resize: true,
..Default::default()
};
let window = Window::new("MicroW8", 320, 240, options)?;
Ok(MicroW8 { std::thread::spawn(move || {
let mut state = State {
engine, engine,
loader_module, loader_module,
window, disable_audio: false,
window_buffer: vec![0u32; 320 * 240],
instance: None, instance: None,
timeout: timeout.unwrap_or(0), timeout: timeout.unwrap_or(0),
disable_audio: false, };
uw8_window::run(move |framebuffer, gamepad, reset| {
if let Ok(module_data) = rx.try_recv() {
if let Err(err) = state.load(&module_data) {
eprintln!("Failed to load module: {}", err);
}
}
state
.run_frame(framebuffer, gamepad, reset)
.unwrap_or_else(|err| {
eprintln!("Runtime error: {}", err);
Instant::now()
}) })
});
});
Ok(MicroW8 { tx })
} }
fn reset(&mut self) { pub fn disable_audio(&mut self) {}
self.instance = None;
for v in &mut self.window_buffer {
*v = 0;
}
}
pub fn disable_audio(&mut self) {
self.disable_audio = true;
}
} }
impl super::Runtime for MicroW8 { impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool { fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(Key::Escape) true
} }
fn load(&mut self, module_data: &[u8]) -> Result<()> { fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.reset(); self.tx.send(module_data.into())?;
Ok(())
}
fn run_frame(&mut self) -> Result<()> {
Ok(())
}
}
struct State {
engine: Engine,
loader_module: Module,
disable_audio: bool,
instance: Option<UW8Instance>,
timeout: u32,
}
impl State {
fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.instance = None;
let mut store = wasmtime::Store::new(&self.engine, ()); let mut store = wasmtime::Store::new(&self.engine, ());
store.set_epoch_deadline(60); store.set_epoch_deadline(60);
@@ -183,7 +180,6 @@ impl super::Runtime for MicroW8 {
end_frame, end_frame,
update, update,
start_time: Instant::now(), start_time: Instant::now(),
next_frame: Instant::now(),
module: module_data.into(), module: module_data.into(),
watchdog, watchdog,
sound, sound,
@@ -191,36 +187,22 @@ impl super::Runtime for MicroW8 {
Ok(()) Ok(())
} }
fn run_frame(
fn run_frame(&mut self) -> Result<()> { &mut self,
let mut result = Ok(()); framebuffer: &mut dyn uw8_window::Framebuffer,
if let Some(mut instance) = self.instance.take() { gamepad: u32,
{ reset: bool,
if let Some(sleep) = instance.next_frame.checked_duration_since(Instant::now()) { ) -> Result<Instant> {
std::thread::sleep(sleep);
}
}
let now = Instant::now(); let now = Instant::now();
let mut result = Ok(now);
if let Some(mut instance) = self.instance.take() {
let time = (now - instance.start_time).as_millis() as i32; let time = (now - instance.start_time).as_millis() as i32;
{ {
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6; let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
instance.next_frame = now + Duration::from_millis((16 - offset) as u64); result = Ok(now + Duration::from_millis((16 - offset) as u64));
} }
{ {
let mut gamepad: u32 = 0;
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;
}
}
let mem = instance.memory.data_mut(&mut instance.store); let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes()); 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(&gamepad.to_le_bytes());
@@ -228,7 +210,9 @@ impl super::Runtime for MicroW8 {
instance.store.set_epoch_deadline(self.timeout as u64); instance.store.set_epoch_deadline(self.timeout as u64);
if let Some(ref update) = instance.update { if let Some(ref update) = instance.update {
result = update.call(&mut instance.store, ()); if let Err(err) = update.call(&mut instance.store, ()) {
result = Err(err);
}
} }
instance.end_frame.call(&mut instance.store, ())?; instance.end_frame.call(&mut instance.store, ())?;
@@ -243,28 +227,18 @@ impl super::Runtime for MicroW8 {
})?; })?;
} }
let framebuffer = &memory[120..(120 + 320 * 240)]; let framebuffer_mem = &memory[120..(120 + 320 * 240)];
let palette = &memory[0x13000..]; let palette_mem = &memory[0x13000..];
for (i, &color_index) in framebuffer.iter().enumerate() { framebuffer.update(framebuffer_mem, palette_mem);
let offset = color_index as usize * 4;
self.window_buffer[i] = 0xff000000
| ((palette[offset] as u32) << 16)
| ((palette[offset + 1] as u32) << 8)
| palette[offset + 2] as u32;
}
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) { if reset {
self.load(&instance.module)?; self.load(&instance.module)?;
} else if result.is_ok() { } else if result.is_ok() {
self.instance = Some(instance); self.instance = Some(instance);
} }
} }
self.window Ok(result?)
.update_with_buffer(&self.window_buffer, 320, 240)?;
result?;
Ok(())
} }
} }

View File

@@ -3,6 +3,17 @@ use std::time::Instant;
use crate::Framebuffer; use crate::Framebuffer;
use minifb::{Key, Window, WindowOptions}; use minifb::{Key, Window, WindowOptions};
static GAMEPAD_KEYS: &[Key] = &[
Key::Up,
Key::Down,
Key::Left,
Key::Right,
Key::Z,
Key::X,
Key::A,
Key::S,
];
pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! { pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
unsafe { unsafe {

View File

@@ -10,6 +10,9 @@ use winit::{
window::{Fullscreen, WindowBuilder}, window::{Fullscreen, WindowBuilder},
}; };
#[cfg(unix)]
use winit::platform::unix::EventLoopExtUnix;
pub struct Window { pub struct Window {
event_loop: EventLoop<()>, event_loop: EventLoop<()>,
window: winit::window::Window, window: winit::window::Window,
@@ -23,7 +26,7 @@ pub struct Window {
impl Window { impl Window {
pub fn new() -> Result<Window> { pub fn new() -> Result<Window> {
async fn create() -> Result<Window> { async fn create() -> Result<Window> {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new_any_thread();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(PhysicalSize::new(640u32, 480)) .with_inner_size(PhysicalSize::new(640u32, 480))
.with_min_inner_size(PhysicalSize::new(320u32, 240)) .with_min_inner_size(PhysicalSize::new(320u32, 240))