8 Commits

23 changed files with 2233 additions and 1280 deletions

2467
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,21 +11,21 @@ native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies]
wasmtime = { version = "0.37.0", optional = true }
wasmtime = { version = "5.0.0", optional = true }
anyhow = "1"
env_logger = "0.9"
env_logger = "0.10"
log = "0.4"
uw8-window = { path = "uw8-window", optional = true }
notify = "4"
pico-args = "0.4"
notify = "5"
pico-args = "0.5"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
wat = "1"
uw8-tool = { path = "uw8-tool" }
same-file = "1"
warp = { version = "0.3.2", optional = true }
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
webbrowser = { version = "0.6.0", optional = true }
warp = { version = "0.3.3", optional = true }
tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
webbrowser = { version = "0.8.6", optional = true }
ansi_term = "0.12.1"
cpal = { version = "0.13.5", optional = true }
rubato = { version = "0.11.0", optional = true }
cpal = { version = "0.14.2", optional = true }
rubato = { version = "0.12.0", optional = true }

View File

@@ -34,6 +34,7 @@ import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32);
import "env.exp" fn exp(f32) -> f32;
import "env.playNote" fn playNote(i32, i32);
import "env.sndGes" fn sndGes(i32) -> f32;
const TIME_MS = 0x40;
const GAMEPAD = 0x44;

View File

@@ -34,6 +34,7 @@
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
(import "env" "exp" (func $exp (param f32) (result f32)))
(import "env" "playNote" (func $playNote (param i32) (param i32)))
(import "env" "sndGes" (func $sndGes (param i32) (result f32)))
;; to use defines, include this file with a preprocessor
;; like gpp (https://logological.org/gpp).

2
platform/Cargo.lock generated
View File

@@ -391,7 +391,7 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "upkr"
version = "0.1.0"
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0"
source = "git+https://github.com/exoticorn/upkr.git?rev=d93aec186c9fb91d962c488682a2db125c61306c#d93aec186c9fb91d962c488682a2db125c61306c"
dependencies = [
"anyhow",
"cdivsufsort",

Binary file not shown.

Binary file not shown.

View File

@@ -10,7 +10,7 @@ const GesState.Size = GesState.Filter + 8*4;
const GesStateOffset = 32;
const GesBufferOffset = 32 + GesState.Size;
export fn gesSnd(t: i32) -> f32 {
export fn sndGes(t: i32) -> f32 {
let baseAddr = 0!0x12c78;
if !(t & 127) {
let i: i32;

View File

@@ -29,6 +29,19 @@ Examplers for older versions:
## Versions
### v0.2.1
* [Web runtime](v0.2.1)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-windows.zip)
Changes:
* new gpu accelerated renderer with (optional) crt filter
* optimized `hline` function, a big speed-up when drawing large filled circles or rectangles
* print fractional size of packed `uw8` cart
### v0.2.0
* [Web runtime](v0.2.0)

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<section>
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
</section>
<a href="v0.2.0">
<a href="v0.2.1">
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
</a>
</div>

View File

@@ -1,18 +1,21 @@
use anyhow::{anyhow, bail, Result};
use notify::{DebouncedEvent, RecommendedWatcher, Watcher};
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc};
pub struct FileWatcher {
watcher: RecommendedWatcher,
watched_files: BTreeSet<PathBuf>,
directories: BTreeSet<PathBuf>,
rx: mpsc::Receiver<DebouncedEvent>,
rx: mpsc::Receiver<Event>,
}
impl FileWatcher {
pub fn new() -> Result<FileWatcher> {
let (tx, rx) = mpsc::channel();
let watcher = notify::watcher(tx, Duration::from_millis(100))?;
let watcher = notify::recommended_watcher(move |res| match res {
Ok(event) => _ = tx.send(event),
Err(err) => eprintln!("Error watching for file changes: {err}"),
})?;
Ok(FileWatcher {
watcher,
watched_files: BTreeSet::new(),
@@ -36,16 +39,20 @@ impl FileWatcher {
}
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
let event = self.rx.try_recv();
match event {
Ok(DebouncedEvent::Create(path) | DebouncedEvent::Write(path)) => {
let handle = same_file::Handle::from_path(&path)?;
for file in &self.watched_files {
if handle == same_file::Handle::from_path(file)? {
return Ok(Some(path));
match self.rx.try_recv() {
Ok(event) => match event.kind {
EventKind::Create(_) | EventKind::Modify(_) => {
for path in event.paths {
let handle = same_file::Handle::from_path(&path)?;
for file in &self.watched_files {
if handle == same_file::Handle::from_path(file)? {
return Ok(Some(path));
}
}
}
}
}
_ => (),
},
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
_ => (),
}

View File

@@ -81,11 +81,12 @@ fn run(mut args: Arguments) -> Result<()> {
#[cfg(not(feature = "native"))]
let run_browser = args.contains(["-b", "--browser"]) || true;
#[allow(unused)]
let disable_audio = args.contains(["-m", "--no-audio"]);
#[cfg(feature = "native")]
let window_config = {
let mut config = WindowConfig::default();
let mut config = uw8_window::WindowConfig::default();
if !run_browser {
config.parse_arguments(&mut args);
}
@@ -98,8 +99,6 @@ fn run(mut args: Arguments) -> Result<()> {
use std::process::exit;
use uw8_window::WindowConfig;
let mut runtime: Box<dyn Runtime> = if !run_browser {
#[cfg(not(feature = "native"))]
unimplemented!();

File diff suppressed because one or more lines are too long

View File

@@ -93,7 +93,7 @@ impl super::Runtime for MicroW8 {
linker.define("env", "memory", memory)?;
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
let load_uw8 = loader_instance.get_typed_func::<i32, i32>(&mut store, "load_uw8")?;
let platform_data = include_bytes!("../platform/bin/platform.uw8");
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
@@ -131,8 +131,8 @@ impl super::Runtime for MicroW8 {
}
let instance = linker.instantiate(&mut store, &module)?;
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
let end_frame = platform_instance.get_typed_func::<(), ()>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), ()>(&mut store, "upd").ok();
let (sound_tx, stream) = if self.disable_audio {
(None, None)
@@ -313,8 +313,8 @@ fn init_sound(
let instance = linker.instantiate(&mut store, module)?;
let snd = instance
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd"))?;
.get_typed_func::<(i32,), f32>(&mut store, "snd")
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32>(&mut store, "sndGes"))?;
let host = cpal::default_host();
let device = host
@@ -342,7 +342,7 @@ fn init_sound(
.ok_or_else(|| anyhow!("Could not find float output config"))?;
let sample_rate = cpal::SampleRate(44100)
.max(config.min_sample_rate())
.max(config.max_sample_rate());
.min(config.max_sample_rate());
let config = config.with_sample_rate(sample_rate);
let buffer_size = match *config.buffer_size() {
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,

View File

@@ -167,6 +167,7 @@ impl BaseModule {
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
for i in functions.len()..64 {
add_function(

View File

@@ -220,6 +220,11 @@ impl<'a> ParsedModule<'a> {
validate_table_section(reader)?;
table_section = Some(Section::new(range, ()));
}
Payload::MemorySection(reader) => {
if reader.get_count() != 0 {
bail!("Found non-empty MemorySection. Memory has to be imported!");
}
}
Payload::ElementSection(mut reader) => {
let mut elements = Vec::with_capacity(reader.get_count() as usize);
for _ in 0..reader.get_count() {

844
uw8-window/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
winit = "0.26.1"
env_logger = "0.9"
winit = "0.27.5"
env_logger = "0.10"
log = "0.4"
pico-args = "0.4"
wgpu = "0.13.1"
pollster = "0.2"
bytemuck = { version = "1.4", features = [ "derive" ] }
pico-args = "0.5"
wgpu = "0.15"
pollster = "0.2.5"
bytemuck = { version = "1.13", features = [ "derive" ] }
anyhow = "1"
minifb = { version = "0.23.0", default-features = false, features = ["x11"] }
winapi = "0.3.9"
winapi = { version = "0.3.9", features = [ "timeapi" ] }

View File

@@ -36,36 +36,36 @@ fn sample_pixel(coords: vec2<i32>, offset: vec4<f32>) -> vec3<f32> {
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let pixel = floor(in.tex_coords);
let o = vec2<f32>(0.5) - (in.tex_coords - pixel);
let pixel = vec2<i32>(pixel);
let pixelf = floor(in.tex_coords);
let o = vec2<f32>(0.5) - (in.tex_coords - pixelf);
let pixel = vec2<i32>(pixelf);
let offset_x = o.xxxx + vec4<f32>(-0.125, 0.375, 0.125, -0.375) * uniforms.texture_scale.z;
let offset_y = o.yyyy + vec4<f32>(-0.375, -0.125, 0.375, 0.125) * uniforms.texture_scale.z;
let offset_x0 = max(abs(offset_x + vec4<f32>(-1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
let offset_x1 = max(abs(offset_x) - vec4<f32>(0.5), vec4<f32>(0.0));
let offset_x2 = max(abs(offset_x + vec4<f32>(1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
var offset_x0 = max(abs(offset_x + vec4<f32>(-1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
var offset_x1 = max(abs(offset_x) - vec4<f32>(0.5), vec4<f32>(0.0));
var offset_x2 = max(abs(offset_x + vec4<f32>(1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
let offset_x0 = offset_x0 * offset_x0;
let offset_x1 = offset_x1 * offset_x1;
let offset_x2 = offset_x2 * offset_x2;
offset_x0 = offset_x0 * offset_x0;
offset_x1 = offset_x1 * offset_x1;
offset_x2 = offset_x2 * offset_x2;
let offset_yr = offset_y + vec4<f32>(-1.0);
let offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
var offset_yr = offset_y + vec4<f32>(-1.0);
offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
var acc = sample_pixel(pixel + vec2<i32>(-1, -1), offset_x0 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(0, -1), offset_x1 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(1, -1), offset_x2 + offset_yr);
let offset_yr = vec4<f32>(0.02) + offset_y * offset_y;
offset_yr = vec4<f32>(0.02) + offset_y * offset_y;
acc = acc + sample_pixel(pixel + vec2<i32>(-1, 0), offset_x0 + offset_yr);
acc = acc + sample_pixel(pixel, offset_x1 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(1, 0), offset_x2 + offset_yr);
let offset_yr = offset_y + vec4<f32>(1.0);
let offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
offset_yr = offset_y + vec4<f32>(1.0);
offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
acc = acc + sample_pixel(pixel + vec2<i32>(-1, 1), offset_x0 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(0, 1), offset_x1 + offset_yr);

View File

@@ -53,8 +53,8 @@ impl Window {
window.set_cursor_visible(false);
let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) };
let instance = wgpu::Instance::new(Default::default());
let surface = unsafe { instance.create_surface(&window) }?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
@@ -71,11 +71,8 @@ impl Window {
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,
..surface.get_default_config(&adapter, window.inner_size().width, window.inner_size().height).expect("Surface incompatible with adapter")
};
let filter: Box<dyn Filter> = create_filter(
@@ -357,6 +354,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::R8Uint,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: None,
view_formats: &[]
});
let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -371,6 +369,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: None,
view_formats: &[]
});
let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -385,6 +384,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
label: None,
view_formats: &[]
});
let framebuffer_texture_view =

View File

@@ -4,31 +4,64 @@ use std::time::Instant;
mod cpu;
mod gpu;
pub struct Window(Box<dyn WindowImpl>);
pub struct Window {
inner: Box<dyn WindowImpl>,
fps_counter: Option<FpsCounter>,
}
struct FpsCounter {
start: Instant,
num_frames: u32,
}
impl Window {
pub fn new(config: WindowConfig) -> Result<Window> {
let fps_counter = if config.fps_counter {
Some(FpsCounter {
start: Instant::now(),
num_frames: 0,
})
} else {
None
};
if config.enable_gpu {
match gpu::Window::new(config) {
Ok(window) => return Ok(Window(Box::new(window))),
Ok(window) => {
return Ok(Window {
inner: Box::new(window),
fps_counter,
})
}
Err(err) => eprintln!(
"Failed to create gpu window: {}\nFalling back tp cpu window",
err
),
}
}
cpu::Window::new().map(|window| Window(Box::new(window)))
cpu::Window::new().map(|window| Window {
inner: Box::new(window),
fps_counter,
})
}
pub fn begin_frame(&mut self) -> Input {
self.0.begin_frame()
self.inner.begin_frame()
}
pub fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
self.0.end_frame(framebuffer, palette, next_frame)
self.inner.end_frame(framebuffer, palette, next_frame);
if let Some(ref mut fps_counter) = self.fps_counter {
fps_counter.num_frames += 1;
let elapsed = fps_counter.start.elapsed().as_secs_f32();
if elapsed >= 1.0 {
println!("fps: {:.1}", fps_counter.num_frames as f32 / elapsed);
fps_counter.num_frames = 0;
fps_counter.start = Instant::now();
}
}
}
pub fn is_open(&self) -> bool {
self.0.is_open()
self.inner.is_open()
}
}
@@ -37,6 +70,7 @@ pub struct WindowConfig {
enable_gpu: bool,
filter: u32,
fullscreen: bool,
fps_counter: bool,
}
impl Default for WindowConfig {
@@ -45,6 +79,7 @@ impl Default for WindowConfig {
enable_gpu: true,
filter: 5,
fullscreen: false,
fps_counter: false,
}
}
}
@@ -66,6 +101,7 @@ impl WindowConfig {
}
}
self.fullscreen = args.contains("--fullscreen");
self.fps_counter = args.contains("--fps");
}
}

View File

@@ -63,7 +63,7 @@ class APU extends AudioWorkletProcessor {
this.memory = memory;
this.snd = instance.exports.snd || platform_instance.exports.gesSnd;
this.snd = instance.exports.snd || platform_instance.exports.sndGes;
this.port.postMessage(2);
}