6 Commits

23 changed files with 2641 additions and 3020 deletions

3200
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 = "19.0.1", optional = true } wasmtime = { version = "5.0.0", optional = true }
anyhow = "1" anyhow = "1"
env_logger = "0.11.3" env_logger = "0.10"
log = "0.4" log = "0.4"
uw8-window = { path = "uw8-window", optional = true } uw8-window = { path = "uw8-window", optional = true }
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.2.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.6", optional = true } warp = { version = "0.3.3", optional = true }
tokio = { version = "1.37.0", features = ["sync", "rt"], optional = true } tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.15", features = ["sync"], optional = true } tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
webbrowser = { version = "0.8.13", optional = true } webbrowser = { version = "0.8.6", optional = true }
ansi_term = "0.12.1" ansi_term = "0.12.1"
cpal = { version = "0.15.3", optional = true } cpal = { version = "0.14.2", optional = true }
rubato = { version = "0.15.0", optional = true } rubato = { version = "0.12.0", optional = true }

View File

@@ -0,0 +1,21 @@
include "../include/microw8-api.cwa"
const SPRITE = 0x20000;
export fn upd() {
cls(0);
let t = time();
let i: i32;
loop spriteLoop {
let inline x = sin(t * -1.3 + i as f32 / 8_f) * 180_f + 160_f;
let inline y = sin(t * 1.7 + i as f32 / 9_f) * 140_f + 120_f;
blitSprite(SPRITE, 16, x as i32, y as i32, 0x100);
branch_if (i +:= 1) < 200: spriteLoop;
}
}
start fn start() {
printChar('OO');
circle(8_f, 8_f, 6_f, 75);
grabSprite(SPRITE, 16, 0, 0, 0);
}

View File

@@ -30,11 +30,13 @@ import "env.printInt" fn printInt(i32);
import "env.setTextColor" fn setTextColor(i32); import "env.setTextColor" fn setTextColor(i32);
import "env.setBackgroundColor" fn setBackgroundColor(i32); import "env.setBackgroundColor" fn setBackgroundColor(i32);
import "env.setCursorPosition" fn setCursorPosition(i32, i32); import "env.setCursorPosition" fn setCursorPosition(i32, i32);
import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32); import "env.rectangleOutline" fn rectangleOutline(f32, f32, f32, f32, i32);
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32); import "env.circleOutline" fn circleOutline(f32, f32, f32, i32);
import "env.exp" fn exp(f32) -> f32; import "env.exp" fn exp(f32) -> f32;
import "env.playNote" fn playNote(i32, i32); import "env.playNote" fn playNote(i32, i32);
import "env.sndGes" fn sndGes(i32) -> f32; import "env.sndGes" fn sndGes(i32) -> f32;
import "env.blitSprite" fn blitSprite(i32, i32, i32, i32, i32);
import "env.grabSprite" fn grabSprite(i32, i32, i32, i32, i32);
const TIME_MS = 0x40; const TIME_MS = 0x40;
const GAMEPAD = 0x44; const GAMEPAD = 0x44;

View File

@@ -30,11 +30,13 @@
(import "env" "setTextColor" (func $setTextColor (param i32))) (import "env" "setTextColor" (func $setTextColor (param i32)))
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32))) (import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32))) (import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
(import "env" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32))) (import "env" "rectangleOutline" (func $rectangleOutline (param f32) (param f32) (param f32) (param f32) (param i32)))
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32))) (import "env" "circleOutline" (func $circleOutline (param f32) (param f32) (param f32) (param i32)))
(import "env" "exp" (func $exp (param f32) (result f32))) (import "env" "exp" (func $exp (param f32) (result f32)))
(import "env" "playNote" (func $playNote (param i32) (param i32))) (import "env" "playNote" (func $playNote (param i32) (param i32)))
(import "env" "sndGes" (func $sndGes (param i32) (result f32))) (import "env" "sndGes" (func $sndGes (param i32) (result f32)))
(import "env" "blitSprite" (func $blitSprite (param i32) (param i32) (param i32) (param i32) (param i32)))
(import "env" "grabSprite" (func $grabSprite (param i32) (param i32) (param i32) (param i32) (param i32)))
;; to use defines, include this file with a preprocessor ;; to use defines, include this file with a preprocessor
;; like gpp (https://logological.org/gpp). ;; like gpp (https://logological.org/gpp).

View File

@@ -5,11 +5,7 @@ pub fn build(b: *std.build.Builder) void {
const lib = b.addSharedLibrary("cart", "main.zig", .unversioned); const lib = b.addSharedLibrary("cart", "main.zig", .unversioned);
lib.setBuildMode(mode); lib.setBuildMode(mode);
lib.setTarget(.{ lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding, .cpu_features_add = std.Target.wasm.featureSet(&.{.nontrapping_fptoint}) });
.cpu_arch = .wasm32,
.os_tag = .freestanding,
.cpu_features_add = std.Target.wasm.featureSet(&.{ .nontrapping_fptoint })
});
lib.import_memory = true; lib.import_memory = true;
lib.initial_memory = 262144; lib.initial_memory = 262144;
lib.max_memory = 262144; lib.max_memory = 262144;
@@ -18,19 +14,13 @@ pub fn build(b: *std.build.Builder) void {
lib.install(); lib.install();
if (lib.install_step) |install_step| { if (lib.install_step) |install_step| {
const run_filter_exports = b.addSystemCommand(&[_][]const u8{ const run_filter_exports = b.addSystemCommand(&[_][]const u8{ "uw8", "filter-exports", "zig-out/lib/cart.wasm", "zig-out/lib/cart-filtered.wasm" });
"uw8", "filter-exports", "zig-out/lib/cart.wasm", "zig-out/lib/cart-filtered.wasm"
});
run_filter_exports.step.dependOn(&install_step.step); run_filter_exports.step.dependOn(&install_step.step);
const run_wasm_opt = b.addSystemCommand(&[_][]const u8{ const run_wasm_opt = b.addSystemCommand(&[_][]const u8{ "wasm-opt", "--enable-nontrapping-float-to-int", "-Oz", "-o", "zig-out/cart.wasm", "zig-out/lib/cart-filtered.wasm" });
"wasm-opt", "-Oz", "-o", "zig-out/cart.wasm", "zig-out/lib/cart-filtered.wasm"
});
run_wasm_opt.step.dependOn(&run_filter_exports.step); run_wasm_opt.step.dependOn(&run_filter_exports.step);
const run_uw8_pack = b.addSystemCommand(&[_][]const u8{ const run_uw8_pack = b.addSystemCommand(&[_][]const u8{ "uw8", "pack", "-l", "9", "zig-out/cart.wasm", "zig-out/cart.uw8" });
"uw8", "pack", "-l", "9", "zig-out/cart.wasm", "zig-out/cart.uw8"
});
run_uw8_pack.step.dependOn(&run_wasm_opt.step); run_uw8_pack.step.dependOn(&run_wasm_opt.step);
const make_opt = b.step("make_opt", "make size optimized cart"); const make_opt = b.step("make_opt", "make size optimized cart");

View File

@@ -1,11 +1,11 @@
extern fn atan2(x: f32, y: f32) f32; extern fn atan2(x: f32, y: f32) f32;
extern fn time() f32; extern fn time() f32;
pub const FRAMEBUFFER: *[320*240]u8 = @intToPtr(*[320*240]u8, 120); pub const FRAMEBUFFER: *[320 * 240]u8 = @intToPtr(*[320 * 240]u8, 120);
export fn upd() void { export fn upd() void {
var i: u32 = 0; var i: u32 = 0;
while(true) { while (true) {
var t = time() * 63.0; var t = time() * 63.0;
var x = @intToFloat(f32, (@intCast(i32, i % 320) - 160)); var x = @intToFloat(f32, (@intCast(i32, i % 320) - 160));
var y = @intToFloat(f32, (@intCast(i32, i / 320) - 120)); var y = @intToFloat(f32, (@intCast(i32, i / 320) - 120));
@@ -13,8 +13,10 @@ export fn upd() void {
var u = atan2(x, y) * 512.0 / 3.141; var u = atan2(x, y) * 512.0 / 3.141;
var c = @intCast(u8, (@floatToInt(i32, d + t * 2.0) ^ @floatToInt(i32, u + t)) & 255) >> 4; var c = @intCast(u8, (@floatToInt(i32, d + t * 2.0) ^ @floatToInt(i32, u + t)) & 255) >> 4;
FRAMEBUFFER[@as(usize, i)] = c; FRAMEBUFFER[i] = c;
i += 1; i += 1;
if(i >= 320*240) { break; } if (i >= 320 * 240) {
break;
}
} }
} }

Binary file not shown.

Binary file not shown.

View File

@@ -62,6 +62,7 @@ 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;
@@ -130,7 +131,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

@@ -171,7 +171,7 @@ export fn rectangle(x: f32, y: f32, w: f32, h: f32, col: i32) {
} }
} }
export fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, col: i32) { export fn rectangleOutline(x: f32, y: f32, w: f32, h: f32, col: i32) {
let xl = nearest(x) as i32; let xl = nearest(x) as i32;
let xr = nearest(x + w) as i32; let xr = nearest(x + w) as i32;
let yt = nearest(y) as i32; let yt = nearest(y) as i32;
@@ -212,7 +212,7 @@ export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
} }
} }
export fn circle_outline(cx: f32, cy: f32, radius: f32, col: i32) { export fn circleOutline(cx: f32, cy: f32, radius: f32, col: i32) {
let prev_w: f32; let prev_w: f32;
let y = clamp(nearest(cy - radius) as i32, -1, 241); let y = clamp(nearest(cy - radius) as i32, -1, 241);
let maxY = clamp(nearest(cy + radius) as i32, -1, 241); let maxY = clamp(nearest(cy + radius) as i32, -1, 241);
@@ -352,6 +352,92 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col); setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
} }
export fn blitSprite(sprite: i32, size: i32, x: i32, y: i32, control: i32) {
let lazy width = size & 65535;
let lazy height = select(size >> 16, size >> 16, width);
let lazy x0 = select(x < 0, -x, 0);
let lazy x1 = select(x + width > 320, 320 - x, width);
let lazy y0 = select(y < 0, -y, 0);
let lazy y1 = select(y + height > 240, 240 - y, height);
let lazy numRows = y1 - y0;
let lazy numCols = x1 - x0;
if numRows <= 0 | numCols <= 0 {
return;
}
let trans = (control & 511) - 256;
let lazy flip_x = 1 - ((control >> 8) & 2);
let lazy flip_y = 1 - ((control >> 9) & 2);
if flip_x < 0 {
sprite += width - 1;
}
if flip_y < 0 {
sprite += (height - 1) * width;
}
let spriteRow = sprite + x0 * flip_x + y0 * flip_y * width;
let screenRow = x + x0 + (y + y0) * 320;
loop yloop {
let lx = 0;
loop xloop {
let lazy col = (spriteRow + lx * flip_x)?0;
if col != trans {
(screenRow + lx)?120 = col;
}
branch_if (lx +:= 1) < numCols: xloop;
}
spriteRow += width * flip_y;
screenRow += 320;
branch_if numRows -:= 1: yloop;
}
}
export fn grabSprite(sprite: i32, size: i32, x: i32, y: i32, control: i32) {
let lazy width = size & 65535;
let lazy height = select(size >> 16, size >> 16, width);
let lazy x0 = select(x < 0, -x, 0);
let lazy x1 = select(x + width > 320, 320 - x, width);
let lazy y0 = select(y < 0, -y, 0);
let lazy y1 = select(y + height > 240, 240 - y, height);
let lazy numRows = y1 - y0;
let lazy numCols = x1 - x0;
if numRows <= 0 | numCols <= 0 {
return;
}
let trans = (control & 511) - 256;
let lazy flip_x = 1 - ((control >> 8) & 2);
let lazy flip_y = 1 - ((control >> 9) & 2);
if flip_x < 0 {
sprite += width - 1;
}
if flip_y < 0 {
sprite += (height - 1) * width;
}
let spriteRow = sprite + x0 * flip_x + y0 * flip_y * width;
let screenRow = x + x0 + (y + y0) * 320;
loop yloop {
let lx = 0;
loop xloop {
let lazy col = (screenRow + lx)?120;
if col != trans {
(spriteRow + lx * flip_x)?0 = col;
}
branch_if (lx +:= 1) < numCols: xloop;
}
spriteRow += width * flip_y;
screenRow += 320;
branch_if numRows -:= 1: yloop;
}
}
////////// //////////
// TEXT // // TEXT //
////////// //////////

View File

@@ -5,7 +5,7 @@ description = "Docs"
# Overview # Overview
MicroW8 loads WebAssembly modules with a maximum size of 256kb. Your module needs to export MicroW8 loads WebAssembly modules with a maximum size of 256kb. You 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.
@@ -146,13 +146,13 @@ Fills the circle at `cx, cy` and with `radius` with the given color index.
(Sets all pixels where the pixel center lies inside the circle.) (Sets all pixels where the pixel center lies inside the circle.)
### fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, color: i32) ### fn rectangleOutline(x: f32, y: f32, w: f32, h: f32, color: i32)
Draws a one pixel outline on the inside of the given rectangle. Draws a one pixel outline on the inside of the given rectangle.
(Draws the outermost pixels that are still inside the rectangle area.) (Draws the outermost pixels that are still inside the rectangle area.)
### fn circle_outline(cx: f32, cy: f32, radius: f32, color: i32) ### fn circleOutline(cx: f32, cy: f32, radius: f32, color: i32)
Draws a one pixel outline on the inside of the given circle. Draws a one pixel outline on the inside of the given circle.
@@ -162,6 +162,21 @@ Draws a one pixel outline on the inside of the given circle.
Draws a line from `x1,y1` to `x2,y2` in the given color index. Draws a line from `x1,y1` to `x2,y2` in the given color index.
### fn blitSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)
Copies the pixel data at `spriteData` onto the screen at `x`, `y`. The size of the sprite is passed as `width | (height << 16)`.
If the height is given as 0, the sprite is is treated as square (width x width).
The control parameter controls masking and flipping of the sprite:
* bits 0-7: color mask index
* bit 8: switch on masked blit (pixel with color mask index are treated as transparent)
* bit 9: flip sprite x
* bit 10: flip sprite y
### fn grabSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)
Copies the pixel data on the screen at `x`, `y` to `spriteData`. Parameters are exactly the same as `blitSprite`.
## Input ## Input
MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof. MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.

View File

@@ -16,14 +16,16 @@ pub struct FileWatcher {
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 debouncer = new_debouncer(Duration::from_millis(100), move |res| match res { let debouncer = new_debouncer(Duration::from_millis(100), None, move |res| match res {
Ok(events) => { Ok(events) => {
for event in events { for event in events {
let _ = tx.send(event); let _ = tx.send(event);
} }
} }
Err(err) => { Err(errs) => {
eprintln!("Error watching for file changes: {err}"); for err in errs {
eprintln!("Error watching for file changes: {err}");
}
} }
})?; })?;
Ok(FileWatcher { Ok(FileWatcher {

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, Func, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, 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(&store, "env", "memory", memory)?; linker.define("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")?;
@@ -181,10 +181,12 @@ impl super::Runtime for MicroW8 {
if let Some(mut instance) = self.instance.take() { 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 next_frame = { let next_frame = {
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6; let frame = (time as u32 as u64 * 6 / 100) as u32;
let max = now + Duration::from_millis(17); let cur_offset = (time as u32).wrapping_sub((frame as u64 * 100 / 6) as u32);
let next_center = now + Duration::from_millis((16 - offset) as u64); let next_time =
next_center.min(max) ((frame as u64 + 1) * 100 / 6 + cur_offset.max(1).min(4) as u64) as u32;
let offset = next_time.wrapping_sub(time as u32);
now + Duration::from_millis(offset as u64)
}; };
{ {
@@ -255,12 +257,15 @@ fn add_native_functions(
} }
})?; })?;
for i in 0..16 { for i in 0..16 {
let global = wasmtime::Global::new( linker.define(
&mut *store, "env",
GlobalType::new(ValType::I32, Mutability::Const), &format!("g_reserved{}", i),
0.into(), wasmtime::Global::new(
&mut *store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?,
)?; )?;
linker.define(&store, "env", &format!("g_reserved{}", i), global)?;
} }
Ok(()) Ok(())
@@ -273,18 +278,14 @@ 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)?;
let exports: Vec<(String, Func)> = platform_instance for export in platform_instance.exports(&mut *store) {
.exports(&mut *store) linker.define(
.map(|e| { "env",
( export.name(),
e.name().to_owned(), export
e.into_func() .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)
@@ -311,7 +312,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(&store, "env", "memory", memory)?; linker.define("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)?;
@@ -328,26 +329,23 @@ 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.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
&& (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);
let prio = if rate >= 44100 { 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 or 16bit signed output config"))?; .ok_or_else(|| anyhow!("Could not find float 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());
@@ -358,7 +356,6 @@ 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()
@@ -378,8 +375,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(true); let input_buffers = rs.input_buffer_allocate();
let output_buffers = rs.output_buffer_allocate(true); let output_buffers = rs.output_buffer_allocate();
Some(Resampler { Some(Resampler {
resampler: rs, resampler: rs,
input_buffers, input_buffers,
@@ -391,130 +388,96 @@ 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(
let mut callback = move |mut outer_buffer: &mut [f32], _: &_| { &config,
let mut first_update = true; move |mut outer_buffer: &mut [f32], _| {
while let Ok(update) = rx.try_recv() { let mut first_update = true;
if first_update { while let Ok(update) = rx.try_recv() {
current_time += update.time.wrapping_sub(current_time) / 8; if first_update {
first_update = false; current_time += update.time.wrapping_sub(current_time) / 8;
} 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();
} }
let duration = if let Some(update) = pending_updates.first() { while !outer_buffer.is_empty() {
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000 store.set_epoch_deadline(30);
} else { while pending_updates
outer_buffer.len() .first()
}; .into_iter()
let step_size = (duration.max(64) * 2).min(outer_buffer.len()); .any(|u| u.time.wrapping_sub(current_time) <= 0)
{
let update = pending_updates.remove(0);
memory.write(&mut store, 80, &update.data).unwrap();
}
let mut buffer = &mut outer_buffer[..step_size]; let duration = if let Some(update) = pending_updates.first() {
((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 {
s.max(-1.0).min(1.0) outer_buffer.len()
};
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].push(clamp_sample( resampler.input_buffers[0]
snd.call(&mut store, (sample_index,)).unwrap_or(0.0), .push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
)); resampler.input_buffers[1]
resampler.input_buffers[1].push(clamp_sample( .push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0), sample_index = sample_index.wrapping_add(2);
)); }
sample_index = sample_index.wrapping_add(2);
}
resampler resampler
.resampler .resampler
.process_into_buffer( .process_into_buffer(
&resampler.input_buffers, &resampler.input_buffers,
&mut resampler.output_buffers, &mut resampler.output_buffers,
None, None,
) )
.unwrap(); .unwrap();
resampler.output_index = 0; resampler.output_index = 0;
} else { } else {
for i in 0..copy_size { for i in 0..copy_size {
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i]; buffer[i * 2] =
buffer[i * 2 + 1] = resampler.output_buffers[0][resampler.output_index + i];
resampler.output_buffers[1][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..];
} }
resampler.output_index += copy_size; }
buffer = &mut buffer[copy_size * 2..]; } else {
for v in buffer {
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
sample_index = sample_index.wrapping_add(1);
} }
} }
} else {
for v in buffer { outer_buffer = &mut outer_buffer[step_size..];
*v = clamp_sample(snd.call(&mut store, (sample_index,)).unwrap_or(0.0)); current_time =
sample_index = sample_index.wrapping_add(1); current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
}
} }
},
outer_buffer = &mut outer_buffer[step_size..]; move |err| {
current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32); dbg!(err);
} },
}; )?;
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

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

View File

@@ -152,14 +152,14 @@ impl BaseModule {
add_function( add_function(
&mut functions, &mut functions,
&type_map, &type_map,
"rectangle_outline", "rectangleOutline",
&[F32, F32, F32, F32, I32], &[F32, F32, F32, F32, I32],
None, None,
); );
add_function( add_function(
&mut functions, &mut functions,
&type_map, &type_map,
"circle_outline", "circleOutline",
&[F32, F32, F32, I32], &[F32, F32, F32, I32],
None, None,
); );
@@ -169,6 +169,21 @@ impl BaseModule {
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None); add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32)); add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
add_function(
&mut functions,
&type_map,
"blitSprite",
&[I32, I32, I32, I32, I32],
None,
);
add_function(
&mut functions,
&type_map,
"grabSprite",
&[I32, I32, I32, I32, I32],
None,
);
for i in functions.len()..64 { for i in functions.len()..64 {
add_function( add_function(
&mut functions, &mut functions,

View File

@@ -1,17 +1,13 @@
use anyhow::Result;
use std::path::Path; use std::path::Path;
use anyhow::Result;
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 let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() {
.exports "upd" => None,
.iter() _ => Some(export.id())
.filter_map(|export| match export.name.as_str() { }).collect();
"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);

1729
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.29.15" winit = "0.27.5"
env_logger = "0.11.3" env_logger = "0.10"
log = "0.4" log = "0.4"
pico-args = "0.5" pico-args = "0.5"
wgpu = "0.19.3" wgpu = "0.15"
pollster = "0.3.0" pollster = "0.2.5"
bytemuck = { version = "1.15", features = [ "derive" ] } bytemuck = { version = "1.13", features = [ "derive" ] }
anyhow = "1" anyhow = "1"
minifb = { version = "0.25.0", default-features = false, features = ["x11"] } minifb = { version = "0.23.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 o = max(0.0, abs(offset) - 0.4); let offset = max(0.0, abs(offset) - 0.4);
return 1.0 / (1.0 + o * o * 16.0); return 1.0 / (1.0 + offset * offset * 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::{sync::Arc, time::Instant}; use std::{num::NonZeroU32, time::Instant};
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, WindowEvent}, event::{Event, VirtualKeyCode, 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<'static>, surface: wgpu::Surface,
_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: Arc<winit::window::Window>, window: winit::window::Window,
gamepads: [u8; 4], gamepads: [u8; 4],
next_frame: Instant, next_frame: Instant,
is_fullscreen: bool, is_fullscreen: bool,
@@ -39,12 +39,9 @@ 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( .with_inner_size(PhysicalSize::new(640u32, 480))
(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 {
@@ -56,10 +53,8 @@ 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 = instance.create_surface(window.clone())?; let surface = unsafe { instance.create_surface(&window) }?;
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower, power_preference: wgpu::PowerPreference::LowPower,
@@ -77,13 +72,7 @@ impl Window {
let surface_config = wgpu::SurfaceConfiguration { let surface_config = wgpu::SurfaceConfiguration {
present_mode: wgpu::PresentMode::AutoNoVsync, present_mode: wgpu::PresentMode::AutoNoVsync,
..surface ..surface.get_default_config(&adapter, window.inner_size().width, window.inner_size().height).expect("Surface incompatible with adapter")
.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(
@@ -121,93 +110,88 @@ 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 self.event_loop.run_return(|event, _, control_flow| {
.set_control_flow(ControlFlow::WaitUntil(self.next_frame)); *control_flow = ControlFlow::WaitUntil(self.next_frame);
while self.is_open { let mut new_filter = None;
let timeout = self.next_frame.saturating_duration_since(Instant::now()); match event {
let status = self.event_loop.pump_events(Some(timeout), |event, elwt| { Event::WindowEvent { event, .. } => match event {
let mut new_filter = None; WindowEvent::Resized(new_size) => {
match event { self.surface_config.width = new_size.width;
Event::WindowEvent { event, .. } => match event { self.surface_config.height = new_size.height;
WindowEvent::Resized(new_size) => { self.surface.configure(&self.device, &self.surface_config);
self.surface_config.width = new_size.width; self.filter.resize(&self.queue, new_size);
self.surface_config.height = new_size.height; }
self.surface.configure(&self.device, &self.surface_config); WindowEvent::CloseRequested => {
self.filter.resize(&self.queue, new_size); self.is_open = false;
} *control_flow = ControlFlow::Exit;
WindowEvent::CloseRequested => { }
elwt.exit(); WindowEvent::KeyboardInput { input, .. } => {
} fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
WindowEvent::KeyboardInput { event, .. } => { match input.scancode {
fn gamepad_button(input: &winit::event::KeyEvent) -> u8 { 44 => 16,
match input.physical_key { 45 => 32,
PhysicalKey::Code(KeyCode::KeyZ) => 16, 30 => 64,
PhysicalKey::Code(KeyCode::KeyX) => 32, 31 => 128,
PhysicalKey::Code(KeyCode::KeyA) => 64, _ => match input.virtual_keycode {
PhysicalKey::Code(KeyCode::KeyS) => 128, Some(VirtualKeyCode::Up) => 1,
_ => match input.logical_key { Some(VirtualKeyCode::Down) => 2,
Key::Named(NamedKey::ArrowUp) => 1, Some(VirtualKeyCode::Left) => 4,
Key::Named(NamedKey::ArrowDown) => 2, Some(VirtualKeyCode::Right) => 8,
Key::Named(NamedKey::ArrowLeft) => 4, _ => 0,
Key::Named(NamedKey::ArrowRight) => 8, },
_ => 0,
},
}
} }
if event.state == winit::event::ElementState::Pressed { }
match event.logical_key { if input.state == winit::event::ElementState::Pressed {
Key::Named(NamedKey::Escape) => { match input.virtual_keycode {
elwt.exit(); Some(VirtualKeyCode::Escape) => {
} self.is_open = false;
Key::Character(ref c) => match c.as_str() { *control_flow = ControlFlow::Exit;
"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),
_ => (),
},
_ => (),
} }
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),
_ => (),
}
self.gamepads[0] |= gamepad_button(&event); self.gamepads[0] |= gamepad_button(&input);
} else { } else {
self.gamepads[0] &= !gamepad_button(&event); self.gamepads[0] &= !gamepad_button(&input);
}
} }
_ => (), }
},
_ => (), _ => (),
},
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 {
if Instant::now() >= self.next_frame { self.filter = create_filter(
break; &self.device,
&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,
@@ -243,12 +227,10 @@ impl WindowImpl for Window {
b: 0.0, b: 0.0,
a: 1.0, a: 1.0,
}), }),
store: wgpu::StoreOp::Store, store: true,
}, },
})], })],
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);
@@ -372,7 +354,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 {
@@ -387,7 +369,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 {
@@ -402,7 +384,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 =
@@ -509,7 +491,7 @@ impl PaletteScreenMode {
&bytemuck::cast_slice(pixels), &bytemuck::cast_slice(pixels),
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(320), bytes_per_row: NonZeroU32::new(320),
rows_per_image: None, rows_per_image: None,
}, },
wgpu::Extent3d { wgpu::Extent3d {
@@ -531,7 +513,7 @@ impl PaletteScreenMode {
&bytemuck::cast_slice(palette), &bytemuck::cast_slice(palette),
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(256 * 4), bytes_per_row: NonZeroU32::new(256 * 4),
rows_per_image: None, rows_per_image: None,
}, },
wgpu::Extent3d { wgpu::Extent3d {
@@ -550,12 +532,10 @@ impl PaletteScreenMode {
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store, store: true,
}, },
})], })],
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(mut config: WindowConfig) -> Result<Window> { pub fn new(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,7 +24,6 @@ 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) => {
@@ -72,7 +71,6 @@ 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 {
@@ -82,7 +80,6 @@ impl Default for WindowConfig {
filter: 5, filter: 5,
fullscreen: false, fullscreen: false,
fps_counter: false, fps_counter: false,
scale: 2.,
} }
} }
} }
@@ -105,10 +102,6 @@ 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);
} }
} }

View File

@@ -274,7 +274,7 @@ export default function MicroW8(screen, config = {}) {
try { try {
let restart = false; let restart = false;
let thisFrame; let nextFrame = 0;
if (!isPaused) { if (!isPaused) {
let gamepads = navigator.getGamepads(); let gamepads = navigator.getGamepads();
let gamepad = 0; let gamepad = 0;
@@ -321,13 +321,12 @@ export default function MicroW8(screen, config = {}) {
} }
canvasCtx.putImageData(imageData, 0, 0); canvasCtx.putImageData(imageData, 0, 0);
let timeOffset = ((time * 6) % 100 - 50) / 6; let thisFrame = Math.floor(time * 6 / 100);
thisFrame = startTime + time - timeOffset / 8; let timeOffset = time - thisFrame * 100 / 6;
} else { nextFrame = Math.ceil(startTime + (thisFrame + 1) * 100 / 6 + Math.min(4, timeOffset));
thisFrame = Date.now();
} }
let now = Date.now(); let now = Date.now();
let nextFrame = Math.max(thisFrame + timePerFrame, now); nextFrame = Math.max(nextFrame, now);
if (restart) { if (restart) {
runModule(currentData); runModule(currentData);