6 Commits

Author SHA1 Message Date
BjoernSchilberg
35aeebd0b6 Merge 937ccf60c9 into fb3fd5e8bb 2024-06-20 03:58:53 +00:00
fb3fd5e8bb add frame counter at memory location 72 2024-06-16 14:10:18 +02:00
b85f96b0bb add support to output sound on devices that only offer surround configs 2024-06-16 13:21:23 +02:00
b69055b58d update to git version of wasmtime
fixes performance regressions since microw8 0.2.1
2024-06-16 13:07:29 +02:00
0878aa3f02 add support for mono-only audio devices 2024-05-26 12:54:59 +02:00
BjoernSchilberg
937ccf60c9 Added a tinygo example. 2023-02-12 22:24:16 +01:00
11 changed files with 914 additions and 517 deletions

1149
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "uw8"
version = "0.3.0"
version = "0.3.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,7 +11,7 @@ native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies]
wasmtime = { version = "19.0.1", optional = true }
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime.git", rev = "0f48f939b9870036562ca02ff21253547a9f1a5c", optional = true }
anyhow = "1"
env_logger = "0.11.3"
log = "0.4"

View File

@@ -3,14 +3,16 @@ include "../include/microw8-api.cwa"
export fn upd() {
let i: i32;
loop pixels {
let inline t = time() * 63 as f32;
let lazy x = (i % 320 - 160) as f32;
let lazy y = (i / 320 - 120) as f32;
let inline d = 40000 as f32 / sqrt(x * x + y * y);
let inline u = atan2(x, y) * (512.0 / 3.141);
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
let inline t = 16!56;
let inline x = (i % 320 - 160) as f32;
let inline y = (i #/ 320 - 120) as f32;
let inline d = 0xa000 as f32 / sqrt(x * x + y * y);
let inline a = atan2(x, y) * 163_f; // (512 / pi)
let inline u = i32.trunc_sat_f32_s(a) + t;
let inline v = i32.trunc_sat_f32_s(d) + t * 2;
let inline c = ((v ^ u) #/ 16) % 16;
i?FRAMEBUFFER = c;
branch_if (i := i + 1) < 320*240: pixels;
}
}
}

6
examples/tinygo/build.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
tinygo build -o cart.wasm -target target.json ./main.go
uw8 filter-exports cart.wasm cart.wasm && \
#wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
uw8 pack -l 9 cart.wasm cart.uw8

38
examples/tinygo/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"math"
"unsafe"
)
//go:wasm-module env
//export atan2
func atan2(x, y float32) float32
//go:wasm-module env
//export time
func time() float32
func sqrt(v float32) float32 {
return float32(math.Sqrt(float64(v)))
}
var FRAMEBUFFER = (*[320 * 240]byte)(unsafe.Pointer(uintptr(120)))
//export upd
func upd() {
var i int
for i < 320*240 {
t := time() * 63.0
x := float32(i%320 - 160)
y := float32(i/320 - 120)
d := float32(40000.0) / sqrt(x*x+y*y)
u := atan2(x, y) * 512.0 / 3.141
c := uint8((int(d+t*2.0) ^ int(u+t)) >> 4)
FRAMEBUFFER[i] = c
i++
}
}
func main() {
}

View File

@@ -0,0 +1,23 @@
{
"llvm-target": "wasm32--wasi",
"build-tags": [ "tinygo.wasm" ],
"goos": "js",
"goarch": "wasm",
"linker": "wasm-ld",
"libc": "wasi-libc",
"cflags": [
"--target=wasm32--wasi",
"--sysroot={root}/lib/wasi-libc/sysroot",
"-Oz"
],
"ldflags": [
"--no-entry",
"--export-all",
"--import-memory",
"--initial-memory=262144",
"--global-base=81920",
"-zstack-size=4096",
"--strip-all"
],
"wasm-abi": "js"
}

View File

@@ -21,7 +21,7 @@ loaded.
00000-00040: user memory
00040-00044: time since module start in ms
00044-0004c: gamepad state
0004c-00050: reserved
0004c-00050: number of frames since module start
00050-00070: sound data (synced to sound thread)
00070-00078: reserved
00078-12c78: frame buffer

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@ use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
use std::{thread, time::Instant};
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use cpal::traits::*;
use rubato::Resampler;
use uw8_window::{Window, WindowConfig};
@@ -27,6 +27,7 @@ struct UW8Instance {
end_frame: TypedFunc<(), ()>,
update: Option<TypedFunc<(), ()>>,
start_time: Instant,
frame_counter: u32,
watchdog: Arc<Mutex<UW8WatchDog>>,
sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
}
@@ -159,6 +160,7 @@ impl super::Runtime for MicroW8 {
end_frame,
update,
start_time: Instant::now(),
frame_counter: 0,
watchdog,
sound_tx,
});
@@ -191,8 +193,11 @@ impl super::Runtime for MicroW8 {
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(&input.gamepads);
mem[72..76].copy_from_slice(&instance.frame_counter.to_le_bytes());
}
instance.frame_counter = instance.frame_counter.wrapping_add(1);
instance.store.set_epoch_deadline(self.timeout as u64);
if let Some(ref update) = instance.update {
if let Err(err) = update.call(&mut instance.store, ()) {
@@ -328,26 +333,37 @@ fn init_sound(
let mut configs: Vec<_> = device
.supported_output_configs()?
.filter(|config| {
config.channels() == 2
&& (config.sample_format() == cpal::SampleFormat::F32
|| config.sample_format() == cpal::SampleFormat::I16)
config.sample_format() == cpal::SampleFormat::F32
|| config.sample_format() == cpal::SampleFormat::I16
})
.collect();
if configs.is_empty() {
eprintln!(
"No suitable audio output config found on device \"{}\", available configs:",
device.name()?
);
for config in device.supported_output_configs()? {
eprintln!(" {}ch {}", config.channels(), config.sample_format());
}
bail!("Failed to configure audio out");
}
configs.sort_by_key(|config| {
let rate = 44100
.max(config.min_sample_rate().0)
.min(config.max_sample_rate().0);
let prio = if rate >= 44100 {
let rate_prio = if rate >= 44100 {
rate - 44100
} else {
(44100 - rate) * 1000
};
prio + (config.sample_format() == cpal::SampleFormat::I16) as u32
let format_prio = (config.sample_format() == cpal::SampleFormat::I16) as u32;
let channels_prio = (config.channels() != 2) as u32 * 16777216;
rate_prio + format_prio + channels_prio
});
let config = configs
.into_iter()
.next()
.ok_or_else(|| anyhow!("Could not find float or 16bit signed output config"))?;
let config = configs.into_iter().next().unwrap();
let sample_rate = cpal::SampleRate(44100)
.max(config.min_sample_rate())
.min(config.max_sample_rate());
@@ -359,6 +375,7 @@ fn init_sound(
}
};
let sample_format = config.sample_format();
let num_channels = config.channels();
let config = cpal::StreamConfig {
buffer_size,
..config.config()
@@ -392,7 +409,7 @@ fn init_sound(
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
let mut current_time = 0;
let mut callback = move |mut outer_buffer: &mut [f32], _: &_| {
let mut callback = move |mut outer_buffer: &mut [f32]| {
let mut first_update = true;
while let Ok(update) = rx.try_recv() {
if first_update {
@@ -484,36 +501,126 @@ fn init_sound(
current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
}
};
let stream = if sample_format == cpal::SampleFormat::F32 {
device.build_output_stream(
&config,
callback,
move |err| {
dbg!(err);
},
None,
)?
} else {
device.build_output_stream(
&config,
move |mut buffer: &mut [i16], info| {
let mut float_buffer = [0f32; 256];
while !buffer.is_empty() {
let step_size = buffer.len().min(float_buffer.len());
let step_buffer = &mut float_buffer[..step_size];
callback(step_buffer, info);
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
}
buffer = &mut buffer[step_size..];
}
},
move |err| {
dbg!(err);
},
None,
)?
fn f32_to_i16<F>(mut buffer: &mut [i16], callback: &mut F)
where
F: FnMut(&mut [f32]),
{
let mut float_buffer = [0f32; 256];
while !buffer.is_empty() {
let step_size = buffer.len().min(float_buffer.len());
let step_buffer = &mut float_buffer[..step_size];
callback(step_buffer);
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
}
buffer = &mut buffer[step_size..];
}
}
fn stereo_to_mono<F>(mut buffer: &mut [f32], callback: &mut F)
where
F: FnMut(&mut [f32]),
{
let mut in_buffer = [0f32; 256];
while !buffer.is_empty() {
let step_size = buffer.len().min(in_buffer.len() / 2);
let step_buffer = &mut in_buffer[..step_size * 2];
callback(step_buffer);
for (index, dest) in buffer.iter_mut().take(step_size).enumerate() {
*dest = (step_buffer[index * 2] + step_buffer[index * 2 + 1]) * 0.5;
}
buffer = &mut buffer[step_size..];
}
}
fn stereo_to_surround<F>(mut buffer: &mut [f32], num_channels: usize, callback: &mut F)
where
F: FnMut(&mut [f32]),
{
let mut in_buffer = [0f32; 256];
buffer.fill(0.);
while !buffer.is_empty() {
let step_size = (buffer.len() / num_channels).min(in_buffer.len() / 2);
let step_buffer = &mut in_buffer[..step_size * 2];
callback(step_buffer);
for index in 0..step_size {
buffer[index * num_channels + 0] = step_buffer[index * 2 + 0];
buffer[index * num_channels + 1] = step_buffer[index * 2 + 1];
}
buffer = &mut buffer[step_size * num_channels..];
}
}
let stream = if sample_format == cpal::SampleFormat::F32 {
if num_channels == 2 {
device.build_output_stream(
&config,
move |buffer: &mut [f32], _| callback(buffer),
move |err| {
dbg!(err);
},
None,
)?
} else if num_channels == 1 {
device.build_output_stream(
&config,
move |buffer: &mut [f32], _| stereo_to_mono(buffer, &mut callback),
move |err| {
dbg!(err);
},
None,
)?
} else {
device.build_output_stream(
&config,
move |buffer: &mut [f32], _| {
stereo_to_surround(buffer, num_channels as usize, &mut callback)
},
move |err| {
dbg!(err);
},
None,
)?
}
} else {
if num_channels == 2 {
device.build_output_stream(
&config,
move |buffer: &mut [i16], _| f32_to_i16(buffer, &mut callback),
move |err| {
dbg!(err);
},
None,
)?
} else if num_channels == 1 {
device.build_output_stream(
&config,
move |buffer: &mut [i16], _| {
f32_to_i16(buffer, &mut |b| stereo_to_mono(b, &mut callback))
},
move |err| {
dbg!(err);
},
None,
)?
} else {
device.build_output_stream(
&config,
move |buffer: &mut [i16], _| {
f32_to_i16(buffer, &mut |b| {
stereo_to_surround(b, num_channels as usize, &mut callback)
})
},
move |err| {
dbg!(err);
},
None,
)?
}
};
Ok(Uw8Sound { stream, tx })

View File

@@ -10,7 +10,7 @@
</head>
<body>
<div id="uw8">
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.3.0
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.3.1
</div>
<div id="centered">
<canvas class="screen" id="screen" width="320" height="240">

View File

@@ -240,6 +240,7 @@ export default function MicroW8(screen, config = {}) {
await audioReadyPromise;
let startTime = Date.now();
let frameCounter = 0;
const timePerFrame = 1000 / 60;
@@ -306,6 +307,7 @@ export default function MicroW8(screen, config = {}) {
let time = Date.now() - startTime;
u32Mem[16] = time;
u32Mem[17] = pad | gamepad;
u32Mem[18] = frameCounter++;
if(instance.exports.upd) {
instance.exports.upd();
}