mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
first somewhat working version of uw8 using gpu window
This commit is contained in:
994
Cargo.lock
generated
994
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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 }
|
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user