18 Commits

Author SHA1 Message Date
7003db3a05 update dependencies 2024-04-03 10:26:41 +02:00
8258bc1854 fix typo 2023-09-13 07:36:24 +02:00
a651107104 add start and snd functions to filter exports exclude list 2023-09-03 10:19:30 +02:00
22c35e37f4 remove debug trace 2023-09-02 15:37:27 +02:00
805c939097 add support for sound devices that only accept 16bit audio 2023-08-23 23:03:23 +02:00
440e150896 update dependencies 2023-08-23 00:52:51 +02:00
77b2e27346 clamp snd samples into valid -1.0..1.0 range 2023-08-23 00:20:35 +02:00
09e4fcbf14 add --scale option 2023-08-11 23:52:19 +02:00
dbeb242fb2 add support for br_table instruction when packing cart 2023-03-20 23:08:42 +01:00
4fe4bce0ad token env var has changed 2023-01-31 00:14:25 +01:00
fe86153562 use fixed version of zola-deploy-action 2023-01-31 00:01:07 +01:00
c9dadaca2e try add safe.directory 2023-01-30 09:48:57 +01:00
5dc3e281ce and again 2023-01-30 09:45:28 +01:00
ce3afb821f another attempt changing the owner to fix permission issue 2023-01-30 09:43:54 +01:00
2652a351ad remove chown again 2023-01-30 09:38:57 +01:00
9109722409 fix main.yml 2023-01-30 09:37:12 +01:00
f861c262a1 update ci actions, hopefully fix permission error 2023-01-30 09:15:09 +01:00
bbfb5eba49 add back event debouncing in file watcher 2023-01-30 00:09:25 +01:00
17 changed files with 3022 additions and 2485 deletions

View File

@@ -30,9 +30,9 @@ jobs:
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Cache build dirs - name: Cache build dirs
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: | path: |
~/.cargo/bin/ ~/.cargo/bin/
@@ -44,7 +44,7 @@ jobs:
- name: Build - name: Build
run: cargo build --release --verbose run: cargo build --release --verbose
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: uw8-${{ matrix.build }} name: uw8-${{ matrix.build }}
path: target/release/${{ matrix.exe }} path: target/release/${{ matrix.exe }}

View File

@@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: build_and_deploy - name: build_and_deploy
uses: shalzz/zola-deploy-action@v0.14.1 uses: shalzz/zola-deploy-action@70a101a14bbdeed13e7a42a9ed06b35c9e9e826e
env: env:
# Target branch # Target branch
PAGES_BRANCH: gh-pages PAGES_BRANCH: gh-pages
BUILD_DIR: site BUILD_DIR: site
# Provide personal access token # Provide personal access token
TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3209
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"] browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies] [dependencies]
wasmtime = { version = "5.0.0", optional = true } wasmtime = { version = "19.0.1", optional = true }
anyhow = "1" anyhow = "1"
env_logger = "0.10" env_logger = "0.11.3"
log = "0.4" log = "0.4"
uw8-window = { path = "uw8-window", optional = true } uw8-window = { path = "uw8-window", optional = true }
notify = "5" notify-debouncer-mini = { version = "0.4.1", default-features = false }
pico-args = "0.5" pico-args = "0.5"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" } curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
wat = "1" wat = "1"
uw8-tool = { path = "uw8-tool" } uw8-tool = { path = "uw8-tool" }
same-file = "1" same-file = "1"
warp = { version = "0.3.3", optional = true } warp = { version = "0.3.6", optional = true }
tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true } tokio = { version = "1.37.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.11", features = ["sync"], optional = true } tokio-stream = { version = "0.1.15", features = ["sync"], optional = true }
webbrowser = { version = "0.8.6", optional = true } webbrowser = { version = "0.8.13", optional = true }
ansi_term = "0.12.1" ansi_term = "0.12.1"
cpal = { version = "0.14.2", optional = true } cpal = { version = "0.15.3", optional = true }
rubato = { version = "0.12.0", optional = true } rubato = { version = "0.15.0", optional = true }

View File

@@ -62,7 +62,6 @@ export fn sndGes(t: i32) -> f32 {
let phase = channelState!GesChannelState.Phase; let phase = channelState!GesChannelState.Phase;
let inline pulseWidth = channelReg?1; let inline pulseWidth = channelReg?1;
let phaseShift = (pulseWidth - 128) * 255;
let invPhaseInc = 1 as f32 / phaseInc as f32; let invPhaseInc = 1 as f32 / phaseInc as f32;
i = 0; i = 0;
@@ -131,7 +130,7 @@ export fn sndGes(t: i32) -> f32 {
let phaseInc = (freq * (65536.0 / 44100.0)) as i32; let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
let phase = channelState!GesChannelState.Phase; let phase = channelState!GesChannelState.Phase;
if modSrc > ch { if modSrc < ch {
phase = phase - (phaseInc << 6); phase = phase - (phaseInc << 6);
} }

View File

@@ -5,7 +5,7 @@ description = "Docs"
# Overview # Overview
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export MicroW8 loads WebAssembly modules with a maximum size of 256kb. Your module needs to export
a function `fn upd()` which will be called once per frame. a function `fn upd()` which will be called once per frame.
After calling `upd` MicroW8 will display the 320x240 8bpp framebuffer located After calling `upd` MicroW8 will display the 320x240 8bpp framebuffer located
at offset 120 in memory with the 32bpp palette located at 0x13000. at offset 120 in memory with the 32bpp palette located at 0x13000.

View File

@@ -1,23 +1,33 @@
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use notify::{Event, EventKind, RecommendedWatcher, Watcher}; use notify_debouncer_mini::{
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc}; new_debouncer,
notify::{self, RecommendedWatcher},
DebouncedEvent, DebouncedEventKind, Debouncer,
};
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
pub struct FileWatcher { pub struct FileWatcher {
watcher: RecommendedWatcher, debouncer: Debouncer<RecommendedWatcher>,
watched_files: BTreeSet<PathBuf>, watched_files: BTreeSet<PathBuf>,
directories: BTreeSet<PathBuf>, directories: BTreeSet<PathBuf>,
rx: mpsc::Receiver<Event>, rx: mpsc::Receiver<DebouncedEvent>,
} }
impl FileWatcher { impl FileWatcher {
pub fn new() -> Result<FileWatcher> { pub fn new() -> Result<FileWatcher> {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let watcher = notify::recommended_watcher(move |res| match res { let debouncer = new_debouncer(Duration::from_millis(100), move |res| match res {
Ok(event) => _ = tx.send(event), Ok(events) => {
Err(err) => eprintln!("Error watching for file changes: {err}"), for event in events {
let _ = tx.send(event);
}
}
Err(err) => {
eprintln!("Error watching for file changes: {err}");
}
})?; })?;
Ok(FileWatcher { Ok(FileWatcher {
watcher, debouncer,
watched_files: BTreeSet::new(), watched_files: BTreeSet::new(),
directories: BTreeSet::new(), directories: BTreeSet::new(),
rx, rx,
@@ -29,7 +39,8 @@ impl FileWatcher {
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?; let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
if !self.directories.contains(parent) { if !self.directories.contains(parent) {
self.watcher self.debouncer
.watcher()
.watch(parent, notify::RecursiveMode::NonRecursive)?; .watch(parent, notify::RecursiveMode::NonRecursive)?;
self.directories.insert(parent.to_path_buf()); self.directories.insert(parent.to_path_buf());
} }
@@ -41,13 +52,11 @@ impl FileWatcher {
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> { pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
match self.rx.try_recv() { match self.rx.try_recv() {
Ok(event) => match event.kind { Ok(event) => match event.kind {
EventKind::Create(_) | EventKind::Modify(_) => { DebouncedEventKind::Any => {
for path in event.paths { let handle = same_file::Handle::from_path(&event.path)?;
let handle = same_file::Handle::from_path(&path)?; for file in &self.watched_files {
for file in &self.watched_files { if handle == same_file::Handle::from_path(file)? {
if handle == same_file::Handle::from_path(file)? { return Ok(Some(event.path));
return Ok(Some(path));
}
} }
} }
} }

View File

@@ -7,7 +7,7 @@ use cpal::traits::*;
use rubato::Resampler; use rubato::Resampler;
use uw8_window::{Window, WindowConfig}; use uw8_window::{Window, WindowConfig};
use wasmtime::{ use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, Func, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
}; };
pub struct MicroW8 { pub struct MicroW8 {
@@ -90,7 +90,7 @@ impl super::Runtime for MicroW8 {
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?; let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(&self.engine); let mut linker = wasmtime::Linker::new(&self.engine);
linker.define("env", "memory", memory)?; linker.define(&store, "env", "memory", memory)?;
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?; 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")?;
@@ -255,15 +255,12 @@ fn add_native_functions(
} }
})?; })?;
for i in 0..16 { for i in 0..16 {
linker.define( let global = wasmtime::Global::new(
"env", &mut *store,
&format!("g_reserved{}", i), GlobalType::new(ValType::I32, Mutability::Const),
wasmtime::Global::new( 0.into(),
&mut *store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?,
)?; )?;
linker.define(&store, "env", &format!("g_reserved{}", i), global)?;
} }
Ok(()) Ok(())
@@ -276,14 +273,18 @@ fn instantiate_platform(
) -> Result<wasmtime::Instance> { ) -> Result<wasmtime::Instance> {
let platform_instance = linker.instantiate(&mut *store, &platform_module)?; let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
for export in platform_instance.exports(&mut *store) { let exports: Vec<(String, Func)> = platform_instance
linker.define( .exports(&mut *store)
"env", .map(|e| {
export.name(), (
export e.name().to_owned(),
.into_func() e.into_func()
.expect("platform surely only exports functions"), .expect("platform surely only exports functions"),
)?; )
})
.collect();
for (name, func) in exports {
linker.define(&store, "env", &name, func)?;
} }
Ok(platform_instance) Ok(platform_instance)
@@ -310,7 +311,7 @@ fn init_sound(
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?; let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
let mut linker = wasmtime::Linker::new(engine); let mut linker = wasmtime::Linker::new(engine);
linker.define("env", "memory", memory)?; linker.define(&store, "env", "memory", memory)?;
add_native_functions(&mut linker, &mut store)?; add_native_functions(&mut linker, &mut store)?;
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?; let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
@@ -327,23 +328,26 @@ fn init_sound(
let mut configs: Vec<_> = device let mut configs: Vec<_> = device
.supported_output_configs()? .supported_output_configs()?
.filter(|config| { .filter(|config| {
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32 config.channels() == 2
&& (config.sample_format() == cpal::SampleFormat::F32
|| config.sample_format() == cpal::SampleFormat::I16)
}) })
.collect(); .collect();
configs.sort_by_key(|config| { configs.sort_by_key(|config| {
let rate = 44100 let rate = 44100
.max(config.min_sample_rate().0) .max(config.min_sample_rate().0)
.min(config.max_sample_rate().0); .min(config.max_sample_rate().0);
if rate >= 44100 { let prio = if rate >= 44100 {
rate - 44100 rate - 44100
} else { } else {
(44100 - rate) * 1000 (44100 - rate) * 1000
} };
prio + (config.sample_format() == cpal::SampleFormat::I16) as u32
}); });
let config = configs let config = configs
.into_iter() .into_iter()
.next() .next()
.ok_or_else(|| anyhow!("Could not find float output config"))?; .ok_or_else(|| anyhow!("Could not find float or 16bit signed output config"))?;
let sample_rate = cpal::SampleRate(44100) let sample_rate = cpal::SampleRate(44100)
.max(config.min_sample_rate()) .max(config.min_sample_rate())
.min(config.max_sample_rate()); .min(config.max_sample_rate());
@@ -354,6 +358,7 @@ fn init_sound(
cpal::BufferSize::Fixed(256.max(min).min(max)) cpal::BufferSize::Fixed(256.max(min).min(max))
} }
}; };
let sample_format = config.sample_format();
let config = cpal::StreamConfig { let config = cpal::StreamConfig {
buffer_size, buffer_size,
..config.config() ..config.config()
@@ -373,8 +378,8 @@ fn init_sound(
None None
} else { } else {
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?; let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
let input_buffers = rs.input_buffer_allocate(); let input_buffers = rs.input_buffer_allocate(true);
let output_buffers = rs.output_buffer_allocate(); let output_buffers = rs.output_buffer_allocate(true);
Some(Resampler { Some(Resampler {
resampler: rs, resampler: rs,
input_buffers, input_buffers,
@@ -386,96 +391,130 @@ fn init_sound(
let mut sample_index = 0; let mut sample_index = 0;
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30); let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
let mut current_time = 0; let mut current_time = 0;
let stream = device.build_output_stream(
&config, let mut callback = move |mut outer_buffer: &mut [f32], _: &_| {
move |mut outer_buffer: &mut [f32], _| { let mut first_update = true;
let mut first_update = true; while let Ok(update) = rx.try_recv() {
while let Ok(update) = rx.try_recv() { if first_update {
if first_update { current_time += update.time.wrapping_sub(current_time) / 8;
current_time += update.time.wrapping_sub(current_time) / 8; first_update = false;
first_update = false; }
} pending_updates.push(update);
pending_updates.push(update); }
while !outer_buffer.is_empty() {
store.set_epoch_deadline(30);
while pending_updates
.first()
.into_iter()
.any(|u| u.time.wrapping_sub(current_time) <= 0)
{
let update = pending_updates.remove(0);
memory.write(&mut store, 80, &update.data).unwrap();
} }
while !outer_buffer.is_empty() { let duration = if let Some(update) = pending_updates.first() {
store.set_epoch_deadline(30); ((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
while pending_updates } else {
.first() outer_buffer.len()
.into_iter() };
.any(|u| u.time.wrapping_sub(current_time) <= 0) let step_size = (duration.max(64) * 2).min(outer_buffer.len());
{
let update = pending_updates.remove(0);
memory.write(&mut store, 80, &update.data).unwrap();
}
let duration = if let Some(update) = pending_updates.first() { let mut buffer = &mut outer_buffer[..step_size];
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
{
let mem = memory.data_mut(&mut store);
mem[64..68].copy_from_slice(&current_time.to_le_bytes());
}
fn clamp_sample(s: f32) -> f32 {
if s.is_nan() {
0.0
} else { } else {
outer_buffer.len() s.max(-1.0).min(1.0)
};
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
let mut buffer = &mut outer_buffer[..step_size];
{
let mem = memory.data_mut(&mut store);
mem[64..68].copy_from_slice(&current_time.to_le_bytes());
} }
}
if let Some(ref mut resampler) = resampler { if let Some(ref mut resampler) = resampler {
while !buffer.is_empty() { while !buffer.is_empty() {
let copy_size = resampler.output_buffers[0] let copy_size = resampler.output_buffers[0]
.len() .len()
.saturating_sub(resampler.output_index) .saturating_sub(resampler.output_index)
.min(buffer.len() / 2); .min(buffer.len() / 2);
if copy_size == 0 { if copy_size == 0 {
resampler.input_buffers[0].clear(); resampler.input_buffers[0].clear();
resampler.input_buffers[1].clear(); resampler.input_buffers[1].clear();
for _ in 0..resampler.resampler.input_frames_next() { for _ in 0..resampler.resampler.input_frames_next() {
resampler.input_buffers[0] resampler.input_buffers[0].push(clamp_sample(
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0)); snd.call(&mut store, (sample_index,)).unwrap_or(0.0),
resampler.input_buffers[1] ));
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0)); resampler.input_buffers[1].push(clamp_sample(
sample_index = sample_index.wrapping_add(2); snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0),
} ));
sample_index = sample_index.wrapping_add(2);
resampler
.resampler
.process_into_buffer(
&resampler.input_buffers,
&mut resampler.output_buffers,
None,
)
.unwrap();
resampler.output_index = 0;
} else {
for i in 0..copy_size {
buffer[i * 2] =
resampler.output_buffers[0][resampler.output_index + i];
buffer[i * 2 + 1] =
resampler.output_buffers[1][resampler.output_index + i];
}
resampler.output_index += copy_size;
buffer = &mut buffer[copy_size * 2..];
} }
}
} else { resampler
for v in buffer { .resampler
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0); .process_into_buffer(
sample_index = sample_index.wrapping_add(1); &resampler.input_buffers,
&mut resampler.output_buffers,
None,
)
.unwrap();
resampler.output_index = 0;
} else {
for i in 0..copy_size {
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i];
buffer[i * 2 + 1] =
resampler.output_buffers[1][resampler.output_index + i];
}
resampler.output_index += copy_size;
buffer = &mut buffer[copy_size * 2..];
} }
} }
} else {
outer_buffer = &mut outer_buffer[step_size..]; for v in buffer {
current_time = *v = clamp_sample(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32); sample_index = sample_index.wrapping_add(1);
}
} }
},
move |err| { outer_buffer = &mut outer_buffer[step_size..];
dbg!(err); 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,
)?
};
Ok(Uw8Sound { stream, tx }) Ok(Uw8Sound { stream, tx })
} }

View File

@@ -2,17 +2,18 @@ import "env.memory" memory(4);
import "env.pow" fn pow(f32, f32) -> f32; import "env.pow" fn pow(f32, f32) -> f32;
import "env.sin" fn sin(f32) -> f32; import "env.sin" fn sin(f32) -> f32;
import "env.cls" fn cls(i32); import "env.cls" fn cls(i32);
import "env.exp" fn exp(f32) -> f32;
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32); import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
include "../platform/src/ges.cwa" include "../platform/src/ges.cwa"
export fn snd(t: i32) -> f32 { export fn snd(t: i32) -> f32 {
gesSnd(t) sndGes(t)
} }
export fn upd() { export fn upd() {
80?0 = 32!32 / 200 & 2 | 0x41; 80?0 = 32!32 / 200 & 2 | 0x41;
80?3 = (32!32 / 400)%7*12/7 + 40; 80?3 = (32!32 / 400)%8*12/7 + 40;
let pulse = (32!32 * 256 / 2000) & 511; let pulse = (32!32 * 256 / 2000) & 511;
if pulse >= 256 { if pulse >= 256 {
pulse = 511 - pulse; pulse = 511 - pulse;

1
todo.txt Normal file
View File

@@ -0,0 +1 @@
* add support for 16bit sound (not just float)

View File

@@ -1,13 +1,17 @@
use std::path::Path;
use anyhow::Result; use anyhow::Result;
use std::path::Path;
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> { pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
let mut module = walrus::Module::from_file(in_path)?; let mut module = walrus::Module::from_file(in_path)?;
let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() { let exports_to_delete: Vec<_> = module
"upd" => None, .exports
_ => Some(export.id()) .iter()
}).collect(); .filter_map(|export| match export.name.as_str() {
"start" | "upd" | "snd" => None,
_ => Some(export.id()),
})
.collect();
for id in exports_to_delete { for id in exports_to_delete {
module.exports.delete(id); module.exports.delete(id);
@@ -18,4 +22,4 @@ pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
module.emit_wasm_file(out_path)?; module.emit_wasm_file(out_path)?;
Ok(()) Ok(())
} }

View File

@@ -767,7 +767,10 @@ fn remap_function(
De::End => En::End, De::End => En::End,
De::Br { relative_depth } => En::Br(relative_depth), De::Br { relative_depth } => En::Br(relative_depth),
De::BrIf { relative_depth } => En::BrIf(relative_depth), De::BrIf { relative_depth } => En::BrIf(relative_depth),
De::BrTable { .. } => todo!(), De::BrTable { targets } => En::BrTable(
targets.targets().collect::<Result<Vec<u32>, _>>()?.into(),
targets.default(),
),
De::Return => En::Return, De::Return => En::Return,
De::Call { function_index } => En::Call( De::Call { function_index } => En::Call(
*function_map *function_map

1723
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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
winit = "0.27.5" winit = "0.29.15"
env_logger = "0.10" env_logger = "0.11.3"
log = "0.4" log = "0.4"
pico-args = "0.5" pico-args = "0.5"
wgpu = "0.15" wgpu = "0.19.3"
pollster = "0.2.5" pollster = "0.3.0"
bytemuck = { version = "1.13", features = [ "derive" ] } bytemuck = { version = "1.15", features = [ "derive" ] }
anyhow = "1" anyhow = "1"
minifb = { version = "0.23.0", default-features = false, features = ["x11"] } minifb = { version = "0.25.0", default-features = false, features = ["x11"] }
winapi = { version = "0.3.9", features = [ "timeapi" ] } winapi = { version = "0.3.9", features = [ "timeapi" ] }

View File

@@ -30,8 +30,8 @@ fn row_factor(offset: f32) -> f32 {
} }
fn col_factor(offset: f32) -> f32 { fn col_factor(offset: f32) -> f32 {
let offset = max(0.0, abs(offset) - 0.4); let o = max(0.0, abs(offset) - 0.4);
return 1.0 / (1.0 + offset * offset * 16.0); return 1.0 / (1.0 + o * o * 16.0);
} }
fn sample_screen(tex_coords: vec2<f32>) -> vec4<f32> { fn sample_screen(tex_coords: vec2<f32>) -> vec4<f32> {

View File

@@ -1,16 +1,16 @@
use crate::{Input, WindowConfig, WindowImpl}; use crate::{Input, WindowConfig, WindowImpl};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::{num::NonZeroU32, time::Instant}; use std::{sync::Arc, time::Instant};
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, VirtualKeyCode, WindowEvent}, event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
keyboard::{Key, KeyCode, NamedKey, PhysicalKey},
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::{Fullscreen, WindowBuilder}, window::{Fullscreen, WindowBuilder},
}; };
use winit::platform::run_return::EventLoopExtRunReturn;
mod crt; mod crt;
mod fast_crt; mod fast_crt;
mod square; mod square;
@@ -21,7 +21,7 @@ use square::SquareFilter;
pub struct Window { pub struct Window {
_instance: wgpu::Instance, _instance: wgpu::Instance,
surface: wgpu::Surface, surface: wgpu::Surface<'static>,
_adapter: wgpu::Adapter, _adapter: wgpu::Adapter,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
@@ -29,7 +29,7 @@ pub struct Window {
surface_config: wgpu::SurfaceConfiguration, surface_config: wgpu::SurfaceConfiguration,
filter: Box<dyn Filter>, filter: Box<dyn Filter>,
event_loop: EventLoop<()>, event_loop: EventLoop<()>,
window: winit::window::Window, window: Arc<winit::window::Window>,
gamepads: [u8; 4], gamepads: [u8; 4],
next_frame: Instant, next_frame: Instant,
is_fullscreen: bool, is_fullscreen: bool,
@@ -39,9 +39,12 @@ pub struct Window {
impl Window { impl Window {
pub fn new(window_config: WindowConfig) -> Result<Window> { pub fn new(window_config: WindowConfig) -> Result<Window> {
async fn create(window_config: WindowConfig) -> Result<Window> { async fn create(window_config: WindowConfig) -> Result<Window> {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new()?;
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(PhysicalSize::new(640u32, 480)) .with_inner_size(PhysicalSize::new(
(320. * window_config.scale).round() as u32,
(240. * window_config.scale).round() as u32,
))
.with_min_inner_size(PhysicalSize::new(320u32, 240)) .with_min_inner_size(PhysicalSize::new(320u32, 240))
.with_title("MicroW8") .with_title("MicroW8")
.with_fullscreen(if window_config.fullscreen { .with_fullscreen(if window_config.fullscreen {
@@ -53,8 +56,10 @@ impl Window {
window.set_cursor_visible(false); window.set_cursor_visible(false);
let window = Arc::new(window);
let instance = wgpu::Instance::new(Default::default()); let instance = wgpu::Instance::new(Default::default());
let surface = unsafe { instance.create_surface(&window) }?; let surface = instance.create_surface(window.clone())?;
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower, power_preference: wgpu::PowerPreference::LowPower,
@@ -72,7 +77,13 @@ impl Window {
let surface_config = wgpu::SurfaceConfiguration { let surface_config = wgpu::SurfaceConfiguration {
present_mode: wgpu::PresentMode::AutoNoVsync, present_mode: wgpu::PresentMode::AutoNoVsync,
..surface.get_default_config(&adapter, window.inner_size().width, window.inner_size().height).expect("Surface incompatible with adapter") ..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( let filter: Box<dyn Filter> = create_filter(
@@ -110,88 +121,93 @@ impl Window {
impl WindowImpl for Window { impl WindowImpl for Window {
fn begin_frame(&mut self) -> Input { fn begin_frame(&mut self) -> Input {
let mut reset = false; let mut reset = false;
self.event_loop.run_return(|event, _, control_flow| { self.event_loop
*control_flow = ControlFlow::WaitUntil(self.next_frame); .set_control_flow(ControlFlow::WaitUntil(self.next_frame));
let mut new_filter = None; while self.is_open {
match event { let timeout = self.next_frame.saturating_duration_since(Instant::now());
Event::WindowEvent { event, .. } => match event { let status = self.event_loop.pump_events(Some(timeout), |event, elwt| {
WindowEvent::Resized(new_size) => { let mut new_filter = None;
self.surface_config.width = new_size.width; match event {
self.surface_config.height = new_size.height; Event::WindowEvent { event, .. } => match event {
self.surface.configure(&self.device, &self.surface_config); WindowEvent::Resized(new_size) => {
self.filter.resize(&self.queue, new_size); self.surface_config.width = new_size.width;
} self.surface_config.height = new_size.height;
WindowEvent::CloseRequested => { self.surface.configure(&self.device, &self.surface_config);
self.is_open = false; self.filter.resize(&self.queue, new_size);
*control_flow = ControlFlow::Exit;
}
WindowEvent::KeyboardInput { input, .. } => {
fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
match input.scancode {
44 => 16,
45 => 32,
30 => 64,
31 => 128,
_ => match input.virtual_keycode {
Some(VirtualKeyCode::Up) => 1,
Some(VirtualKeyCode::Down) => 2,
Some(VirtualKeyCode::Left) => 4,
Some(VirtualKeyCode::Right) => 8,
_ => 0,
},
}
} }
if input.state == winit::event::ElementState::Pressed { WindowEvent::CloseRequested => {
match input.virtual_keycode { elwt.exit();
Some(VirtualKeyCode::Escape) => { }
self.is_open = false; WindowEvent::KeyboardInput { event, .. } => {
*control_flow = ControlFlow::Exit; fn gamepad_button(input: &winit::event::KeyEvent) -> u8 {
match input.physical_key {
PhysicalKey::Code(KeyCode::KeyZ) => 16,
PhysicalKey::Code(KeyCode::KeyX) => 32,
PhysicalKey::Code(KeyCode::KeyA) => 64,
PhysicalKey::Code(KeyCode::KeyS) => 128,
_ => match input.logical_key {
Key::Named(NamedKey::ArrowUp) => 1,
Key::Named(NamedKey::ArrowDown) => 2,
Key::Named(NamedKey::ArrowLeft) => 4,
Key::Named(NamedKey::ArrowRight) => 8,
_ => 0,
},
} }
Some(VirtualKeyCode::F) => {
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) => new_filter = Some(1),
Some(VirtualKeyCode::Key2) => new_filter = Some(2),
Some(VirtualKeyCode::Key3) => new_filter = Some(3),
Some(VirtualKeyCode::Key4) => new_filter = Some(4),
Some(VirtualKeyCode::Key5) => new_filter = Some(5),
_ => (),
} }
if event.state == winit::event::ElementState::Pressed {
match event.logical_key {
Key::Named(NamedKey::Escape) => {
elwt.exit();
}
Key::Character(ref c) => match c.as_str() {
"f" => {
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);
}
"r" => reset = true,
"1" => new_filter = Some(1),
"2" => new_filter = Some(2),
"3" => new_filter = Some(3),
"4" => new_filter = Some(4),
"5" => new_filter = Some(5),
_ => (),
},
_ => (),
}
self.gamepads[0] |= gamepad_button(&input); self.gamepads[0] |= gamepad_button(&event);
} else { } else {
self.gamepads[0] &= !gamepad_button(&input); self.gamepads[0] &= !gamepad_button(&event);
}
} }
} _ => (),
},
_ => (), _ => (),
},
Event::RedrawEventsCleared => {
if Instant::now() >= self.next_frame
// workaround needed on Wayland until the next winit release
&& self.window.fullscreen().is_some() == self.is_fullscreen
{
*control_flow = ControlFlow::Exit
}
} }
if let Some(new_filter) = new_filter {
self.filter = create_filter(
&self.device,
&self.palette_screen_mode.screen_view,
self.window.inner_size(),
self.surface_config.format,
new_filter,
);
}
});
match status {
PumpStatus::Exit(_) => self.is_open = false,
_ => (), _ => (),
} }
if let Some(new_filter) = new_filter {
self.filter = create_filter( if Instant::now() >= self.next_frame {
&self.device, break;
&self.palette_screen_mode.screen_view,
self.window.inner_size(),
self.surface_config.format,
new_filter,
);
} }
}); }
Input { Input {
gamepads: self.gamepads, gamepads: self.gamepads,
reset, reset,
@@ -227,10 +243,12 @@ impl WindowImpl for Window {
b: 0.0, b: 0.0,
a: 1.0, a: 1.0,
}), }),
store: true, store: wgpu::StoreOp::Store,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
}); });
self.filter.render(&mut render_pass); self.filter.render(&mut render_pass);
@@ -354,7 +372,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::R8Uint, format: wgpu::TextureFormat::R8Uint,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: None, label: None,
view_formats: &[] view_formats: &[],
}); });
let palette_texture = device.create_texture(&wgpu::TextureDescriptor { let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -369,7 +387,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::Rgba8UnormSrgb, format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: None, label: None,
view_formats: &[] view_formats: &[],
}); });
let screen_texture = device.create_texture(&wgpu::TextureDescriptor { let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -384,7 +402,7 @@ impl PaletteScreenMode {
format: wgpu::TextureFormat::Rgba8UnormSrgb, format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
label: None, label: None,
view_formats: &[] view_formats: &[],
}); });
let framebuffer_texture_view = let framebuffer_texture_view =
@@ -491,7 +509,7 @@ impl PaletteScreenMode {
&bytemuck::cast_slice(pixels), &bytemuck::cast_slice(pixels),
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: NonZeroU32::new(320), bytes_per_row: Some(320),
rows_per_image: None, rows_per_image: None,
}, },
wgpu::Extent3d { wgpu::Extent3d {
@@ -513,7 +531,7 @@ impl PaletteScreenMode {
&bytemuck::cast_slice(palette), &bytemuck::cast_slice(palette),
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: NonZeroU32::new(256 * 4), bytes_per_row: Some(256 * 4),
rows_per_image: None, rows_per_image: None,
}, },
wgpu::Extent3d { wgpu::Extent3d {
@@ -532,10 +550,12 @@ impl PaletteScreenMode {
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Load,
store: true, store: wgpu::StoreOp::Store,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
}); });
render_pass.set_pipeline(&self.pipeline); render_pass.set_pipeline(&self.pipeline);

View File

@@ -15,7 +15,7 @@ struct FpsCounter {
} }
impl Window { impl Window {
pub fn new(config: WindowConfig) -> Result<Window> { pub fn new(mut config: WindowConfig) -> Result<Window> {
let fps_counter = if config.fps_counter { let fps_counter = if config.fps_counter {
Some(FpsCounter { Some(FpsCounter {
start: Instant::now(), start: Instant::now(),
@@ -24,6 +24,7 @@ impl Window {
} else { } else {
None None
}; };
config.scale = config.scale.max(1.).min(20.);
if config.enable_gpu { if config.enable_gpu {
match gpu::Window::new(config) { match gpu::Window::new(config) {
Ok(window) => { Ok(window) => {
@@ -71,6 +72,7 @@ pub struct WindowConfig {
filter: u32, filter: u32,
fullscreen: bool, fullscreen: bool,
fps_counter: bool, fps_counter: bool,
scale: f32,
} }
impl Default for WindowConfig { impl Default for WindowConfig {
@@ -80,6 +82,7 @@ impl Default for WindowConfig {
filter: 5, filter: 5,
fullscreen: false, fullscreen: false,
fps_counter: false, fps_counter: false,
scale: 2.,
} }
} }
} }
@@ -102,6 +105,10 @@ impl WindowConfig {
} }
self.fullscreen = args.contains("--fullscreen"); self.fullscreen = args.contains("--fullscreen");
self.fps_counter = args.contains("--fps"); self.fps_counter = args.contains("--fps");
self.scale = args
.opt_value_from_str("--scale")
.unwrap()
.unwrap_or(self.scale);
} }
} }