35 Commits

Author SHA1 Message Date
1a06ebbc95 update zig example 2023-04-08 15:10:09 +02:00
d1b6fa36e8 update frame pacing calculation in native runtime 2023-04-07 18:33:22 +02:00
b3a8512129 update frame pacing calculation in web runtime
fixes carts running too slow in chrome
2023-04-07 17:08:04 +02:00
c8a9f533ef copy optimized blitSprite impl to grabSprite, add simple docs 2023-04-03 23:44:12 +02:00
4a1d607bcb optimize blitSprite 2023-04-03 00:09:02 +02:00
bbb75838cd first unoptimized version of blitSprite & grabSprite 2023-04-02 23:21:31 +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
9e19ff1761 prepare for 0.2.2 release 2023-01-28 12:47:16 +01:00
5d85aeac09 call an exported start function if it exists 2023-01-28 12:31:16 +01:00
30eb953d5d remove test code that was accidentally committed 2023-01-28 11:37:05 +01:00
abdf780533 only open browser once cart has been compiled succesfully 2023-01-28 11:33:49 +01:00
fc05a54e2c fix control codes 4-6 as parameters to other control codes 2023-01-28 10:58:28 +01:00
b33099c828 update more dependencies 2023-01-28 02:11:25 +01:00
d478d3ad49 enable timeapi in winapi crate 2023-01-27 00:13:32 +01:00
502852e59a update uw8-window dependencies 2023-01-26 23:40:54 +01:00
5efa8b3465 first batch of dependency updates 2023-01-26 22:45:34 +01:00
daf2a02cd8 fix sndGes name & add missing auto-import 2023-01-23 23:21:45 +01:00
8d5374a867 add support to ignore empty memory section when packing 2022-11-03 23:27:53 +01:00
142b6a4c15 fix typo in sample rate selection 2022-10-30 00:07:58 +02:00
877fceb089 add --fps parameter to output fps 2022-07-25 23:44:06 +02:00
f0ba0f2b99 update site with 0.2.1 release 2022-07-25 08:46:23 +02:00
760664eb77 add some command line switches for the gpu window 2022-07-23 22:34:59 +02:00
465e66ff4b slight improvement to packed size display 2022-07-23 00:30:14 +02:00
e4579d81bc add chromatic version of fast crt shader + auto crt shader 2022-07-21 23:03:52 +02:00
1f5042059c fix inputs getting stuck 2022-07-21 19:37:36 +02:00
499bb02f2c restructure control flow of uw8-window to hopefully make it work on MacOS 2022-07-21 08:51:17 +02:00
49 changed files with 3408 additions and 1846 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 }}

2522
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "uw8" name = "uw8"
version = "0.2.1" version = "0.2.2"
edition = "2021" 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
@@ -11,19 +11,21 @@ native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
browser = ["warp", "tokio", "tokio-stream", "webbrowser"] browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
[dependencies] [dependencies]
wasmtime = { version = "0.37.0", optional = true } wasmtime = { version = "5.0.0", optional = true }
anyhow = "1" anyhow = "1"
env_logger = "0.10"
log = "0.4"
uw8-window = { path = "uw8-window", optional = true } uw8-window = { path = "uw8-window", optional = true }
notify = "4" notify-debouncer-mini = { version = "0.2.1", default-features = false }
pico-args = "0.4" 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.2", optional = true } warp = { version = "0.3.3", optional = true }
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true } tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true } tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
webbrowser = { version = "0.6.0", optional = true } webbrowser = { version = "0.8.6", optional = true }
ansi_term = "0.12.1" ansi_term = "0.12.1"
cpal = { version = "0.13.5", optional = true } cpal = { version = "0.14.2", optional = true }
rubato = { version = "0.11.0", optional = true } rubato = { version = "0.12.0", optional = true }

View File

@@ -15,9 +15,9 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
## Downloads ## Downloads
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz) * [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz) * [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip) * [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
The download includes The download includes
@@ -43,6 +43,27 @@ Options:
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow. -l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
-o FILE, --output FILE : Write the loaded and optionally packed cart back to disk. -o FILE, --output FILE : Write the loaded and optionally packed cart back to disk.
when using the native runtime:
-m, --no-audio : Disable audio, also reduces cpu load a bit
--no-gpu : Force old cpu-only window code
--filter FILTER : Select an upscale filter at startup
--fullscreen : Start in fullscreen mode
Note that the cpu-only window does not support fullscreen nor upscale filters.
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
you can just pass "--filter nearest" or "--filter 1".
The upscale filter options are:
1, nearest : Anti-aliased nearest filter
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
uw8 pack [<options>] <infile> <outfile> uw8 pack [<options>] <infile> <outfile>

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,10 +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.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,10 +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" "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

@@ -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;
}
} }
} }

186
platform/Cargo.lock generated
View File

@@ -110,9 +110,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.0" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -151,32 +151,39 @@ dependencies = [
"anyhow", "anyhow",
"ariadne", "ariadne",
"chumsky", "chumsky",
"pico-args", "pico-args 0.4.2",
"wasm-encoder 0.10.0", "wasm-encoder 0.10.0",
"wasmparser 0.83.0", "wasmparser 0.83.0",
] ]
[[package]] [[package]]
name = "fallible_collections" name = "fallible_collections"
version = "0.4.4" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52db5973b6a19247baf19b30f41c23a1bfffc2e9ce0a5db2f60e3cd5dc8895f7" checksum = "3f57ccc32870366ae684be48b32a1a2e196f98a42a9b4361fe77e13fd4a34755"
dependencies = [ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.22" version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [ dependencies = [
"cfg-if",
"crc32fast", "crc32fast",
"libc",
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.3" version = "0.2.3"
@@ -190,9 +197,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [ dependencies = [
"ahash 0.7.6", "ahash 0.7.6",
] ]
@@ -212,6 +219,26 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -225,17 +252,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]] [[package]]
name = "libc" name = "lexopt"
version = "0.2.112" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]] [[package]]
name = "lodepng" name = "lodepng"
version = "3.4.7" version = "3.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24844d5c0b922ddd52fb5bf0964a4c7f8e799a946ec01bb463771eb04fc1a323" checksum = "f0ad39f75bbaa4b10bb6f2316543632a8046a5bcf9c785488d79720b21f044f8"
dependencies = [ dependencies = [
"crc32fast",
"fallible_collections", "fallible_collections",
"flate2", "flate2",
"libc", "libc",
@@ -253,12 +287,11 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.4" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [ dependencies = [
"adler", "adler",
"autocfg",
] ]
[[package]] [[package]]
@@ -288,12 +321,24 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]] [[package]]
name = "pico-args" name = "pico-args"
version = "0.4.2" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "platform" name = "platform"
version = "0.1.0" version = "0.1.0"
@@ -330,9 +375,9 @@ dependencies = [
[[package]] [[package]]
name = "rgb" name = "rgb"
version = "0.8.31" version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a374af9a0e5fdcdd98c1c7b64f05004f9ea2555b6c75f211daa81268a3c50f1" checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
] ]
@@ -357,6 +402,26 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@@ -376,6 +441,36 @@ dependencies = [
"crunchy", "crunchy",
] ]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.9.0" version = "1.9.0"
@@ -390,13 +485,24 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]] [[package]]
name = "upkr" name = "upkr"
version = "0.1.0" version = "0.2.1"
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0" source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cdivsufsort", "cdivsufsort",
"pbr", "lexopt",
"pico-args", "thiserror",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
] ]
[[package]] [[package]]
@@ -405,11 +511,11 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"pbr", "pbr",
"pico-args", "pico-args 0.5.0",
"upkr", "upkr",
"walrus", "walrus",
"wasm-encoder 0.8.0", "wasm-encoder 0.22.0",
"wasmparser 0.81.0", "wasmparser 0.99.0",
] ]
[[package]] [[package]]
@@ -452,18 +558,18 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wasm-encoder" name = "wasm-encoder"
version = "0.8.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0" checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
dependencies = [ dependencies = [
"leb128", "leb128",
] ]
[[package]] [[package]]
name = "wasm-encoder" name = "wasm-encoder"
version = "0.10.0" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab" checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
dependencies = [ dependencies = [
"leb128", "leb128",
] ]
@@ -474,18 +580,22 @@ version = "0.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6" checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
[[package]]
name = "wasmparser"
version = "0.81.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.83.0" version = "0.83.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
[[package]]
name = "wasmparser"
version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
dependencies = [
"indexmap",
"url",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -9,4 +9,4 @@ edition = "2021"
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="0e7ea50" } curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="0e7ea50" }
uw8-tool = { path="../uw8-tool" } uw8-tool = { path="../uw8-tool" }
anyhow = "1" anyhow = "1"
lodepng = "3.4" lodepng = "3.7.2"

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 GesStateOffset = 32;
const GesBufferOffset = 32 + GesState.Size; const GesBufferOffset = 32 + GesState.Size;
export fn gesSnd(t: i32) -> f32 { export fn sndGes(t: i32) -> f32 {
let baseAddr = 0!0x12c78; let baseAddr = 0!0x12c78;
if !(t & 127) { if !(t & 127) {
let i: i32; let i: i32;

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 //
////////// //////////
@@ -372,16 +458,7 @@ export fn printChar(char: i32) {
global mut controlCodeLength = 0; global mut controlCodeLength = 0;
fn printSingleChar(char: i32) { fn printSingleChar(char: i32) {
if char >= 4 & char <= 6 { if outputChannel >= 2 & (char < 4 | char > 6) {
outputChannel = char - 4;
if !outputChannel {
textCursorX = 0;
textCursorY = 0;
}
return;
}
if outputChannel >= 2 {
logChar(char); logChar(char);
return; return;
} }
@@ -399,6 +476,15 @@ fn printSingleChar(char: i32) {
return; return;
} }
if char >= 4 & char <= 6 {
outputChannel = char - 4;
if !outputChannel {
textCursorX = 0;
textCursorY = 0;
}
return;
}
if char == 7 { if char == 7 {
80?0 = 80?0 ^ 2; 80?0 = 80?0 ^ 2;
return; return;

View File

@@ -14,6 +14,8 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
* Memory: 256KB * Memory: 256KB
* Gamepad input (D-Pad + 4 Buttons) * Gamepad input (D-Pad + 4 Buttons)
For detailed [documentation see here](docs).
## Examples ## Examples
* [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound * [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound
* [Fireworks](v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022. * [Fireworks](v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
@@ -29,6 +31,33 @@ Examplers for older versions:
## Versions ## Versions
### v0.2.2
* [Web runtime](v0.2.2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
Changes:
* call `start` function after loading cart if the cart exports one
* fix `sndGes` having the wrong name and not being included in the auto imports
* fix control codes 4-6 (change text output mode) being invoked when used as parameters in other control sequences
* only open browser window once a cart was compiled sucessfully when running with `-b`
### 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 ### v0.2.0
* [Web runtime](v0.2.0) * [Web runtime](v0.2.0)

View File

@@ -12,6 +12,9 @@ at offset 120 in memory with the 32bpp palette located at 0x13000.
The memory has to be imported as `env` `memory` and has a maximum size of 256kb (4 pages). The memory has to be imported as `env` `memory` and has a maximum size of 256kb (4 pages).
If the module exports a function called `start`, it will be called once after the module is
loaded.
# Memory map # Memory map
``` ```
@@ -143,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.
@@ -159,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.
@@ -436,6 +454,30 @@ and execution of the cart is stopped. Defaults to 30 (0.5s)
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow. * `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
* `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk. * `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk.
when using the native runtime:
* `-m`, `--no-audio`: Disable audio, also reduces cpu load a bit
* `--no-gpu`: Force old cpu-only window code
* `--filter FILTER`: Select an upscale filter at startup
* `--fullscreen`: Start in fullscreen mode
Note that the cpu-only window does not support fullscreen nor upscale filters.
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
you can just pass `--filter nearest` or `--filter 1`.
The upscale filter options are:
```
1, nearest : Anti-aliased nearest filter
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
```
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
## `uw8 pack` ## `uw8 pack`
Usage: Usage:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,9 +1,13 @@
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use notify::{DebouncedEvent, RecommendedWatcher, Watcher}; use notify_debouncer_mini::{
new_debouncer,
notify::{self, RecommendedWatcher},
DebouncedEvent, DebouncedEventKind, Debouncer,
};
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration}; 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<DebouncedEvent>, rx: mpsc::Receiver<DebouncedEvent>,
@@ -12,9 +16,20 @@ 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 watcher = notify::watcher(tx, Duration::from_millis(100))?; let debouncer = new_debouncer(Duration::from_millis(100), None, move |res| match res {
Ok(events) => {
for event in events {
let _ = tx.send(event);
}
}
Err(errs) => {
for err in errs {
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,
@@ -26,7 +41,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());
} }
@@ -36,16 +52,18 @@ impl FileWatcher {
} }
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> { pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
let event = self.rx.try_recv(); match self.rx.try_recv() {
match event { Ok(event) => match event.kind {
Ok(DebouncedEvent::Create(path) | DebouncedEvent::Write(path)) => { DebouncedEventKind::Any => {
let handle = same_file::Handle::from_path(&path)?; let handle = same_file::Handle::from_path(&event.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(path)); return Ok(Some(event.path));
} }
} }
} }
_ => (),
},
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"), Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
_ => (), _ => (),
} }

View File

@@ -13,6 +13,7 @@ use uw8::RunWebServer;
use uw8::Runtime; use uw8::Runtime;
fn main() -> Result<()> { fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default()).init();
let mut args = Arguments::from_env(); let mut args = Arguments::from_env();
// try to enable ansi support in win10 cmd shell // try to enable ansi support in win10 cmd shell
@@ -35,7 +36,7 @@ fn main() -> Result<()> {
println!(); println!();
println!("Usage:"); println!("Usage:");
#[cfg(any(feature = "native", feature = "browser"))] #[cfg(any(feature = "native", feature = "browser"))]
println!(" uw8 run [-t/--timeout <frames>] [--no-gpu] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>"); println!(" uw8 run [-t/--timeout <frames>] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>"); println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
println!(" uw8 unpack <in-file> <out-file>"); println!(" uw8 unpack <in-file> <out-file>");
println!(" uw8 compile [-d/--debug] <in-file> <out-file>"); println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
@@ -54,8 +55,6 @@ fn run(mut args: Arguments) -> Result<()> {
let watch_mode = args.contains(["-w", "--watch"]); let watch_mode = args.contains(["-w", "--watch"]);
#[allow(unused)] #[allow(unused)]
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?; let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
#[allow(unused)]
let gpu = !args.contains("--no-gpu");
let mut config = Config::default(); let mut config = Config::default();
if args.contains(["-p", "--pack"]) { if args.contains(["-p", "--pack"]) {
@@ -82,7 +81,17 @@ fn run(mut args: Arguments) -> Result<()> {
#[cfg(not(feature = "native"))] #[cfg(not(feature = "native"))]
let run_browser = args.contains(["-b", "--browser"]) || true; let run_browser = args.contains(["-b", "--browser"]) || true;
let disable_audio = args.contains(["-m", "--disable-audio"]); #[allow(unused)]
let disable_audio = args.contains(["-m", "--no-audio"]);
#[cfg(feature = "native")]
let window_config = {
let mut config = uw8_window::WindowConfig::default();
if !run_browser {
config.parse_arguments(&mut args);
}
config
};
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
@@ -95,7 +104,7 @@ fn run(mut args: Arguments) -> Result<()> {
unimplemented!(); unimplemented!();
#[cfg(feature = "native")] #[cfg(feature = "native")]
{ {
let mut microw8 = MicroW8::new(timeout, gpu)?; let mut microw8 = MicroW8::new(timeout, window_config)?;
if disable_audio { if disable_audio {
microw8.disable_audio(); microw8.disable_audio();
} }
@@ -167,7 +176,8 @@ fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>
if let Some(ref pack_config) = config.pack { if let Some(ref pack_config) = config.pack {
cart = uw8_tool::pack(&cart, pack_config)?; cart = uw8_tool::pack(&cart, pack_config)?;
println!( println!(
"\npacked size: {:.2} bytes", "\npacked size: {} bytes ({:.2})",
cart.len(),
uw8_tool::compressed_size(&cart) uw8_tool::compressed_size(&cart)
); );
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,23 +5,20 @@ use std::{thread, time::Instant};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use cpal::traits::*; use cpal::traits::*;
use rubato::Resampler; use rubato::Resampler;
use uw8_window::{Window, WindowConfig};
use wasmtime::{ use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
}; };
pub struct MicroW8 { pub struct MicroW8 {
tx: mpsc::SyncSender<Option<UW8Instance>>, window: Window,
rx: mpsc::Receiver<UIEvent>,
stream: Option<cpal::Stream>, stream: Option<cpal::Stream>,
engine: Engine, engine: Engine,
loader_module: Module, loader_module: Module,
disable_audio: bool, disable_audio: bool,
module_data: Option<Vec<u8>>, module_data: Option<Vec<u8>>,
} timeout: u32,
instance: Option<UW8Instance>,
enum UIEvent {
Error(Result<()>),
Reset,
} }
struct UW8Instance { struct UW8Instance {
@@ -48,7 +45,7 @@ struct UW8WatchDog {
} }
impl MicroW8 { impl MicroW8 {
pub fn new(timeout: Option<u32>, gpu: bool) -> Result<MicroW8> { pub fn new(timeout: Option<u32>, window_config: WindowConfig) -> Result<MicroW8> {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
config.cranelift_opt_level(wasmtime::OptLevel::Speed); config.cranelift_opt_level(wasmtime::OptLevel::Speed);
if timeout.is_some() { if timeout.is_some() {
@@ -59,39 +56,17 @@ impl MicroW8 {
let loader_module = let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let (to_ui_tx, to_ui_rx) = mpsc::sync_channel(2); let window = Window::new(window_config)?;
let (from_ui_tx, from_ui_rx) = mpsc::sync_channel(1);
std::thread::spawn(move || {
let mut state = State {
instance: None,
timeout: timeout.unwrap_or(0),
};
uw8_window::run(gpu, move |framebuffer, gamepad, reset| {
while let Ok(instance) = to_ui_rx.try_recv() {
state.instance = instance;
}
if reset {
from_ui_tx.send(UIEvent::Reset).unwrap();
}
state.run_frame(framebuffer, gamepad).unwrap_or_else(|err| {
from_ui_tx.send(UIEvent::Error(Err(err))).unwrap();
Instant::now()
})
});
});
Ok(MicroW8 { Ok(MicroW8 {
tx: to_ui_tx, window,
rx: from_ui_rx,
stream: None, stream: None,
engine, engine,
loader_module, loader_module,
disable_audio: false, disable_audio: false,
module_data: None, module_data: None,
timeout: timeout.unwrap_or(0),
instance: None,
}) })
} }
@@ -102,12 +77,12 @@ impl MicroW8 {
impl super::Runtime for MicroW8 { impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool { fn is_open(&self) -> bool {
true self.window.is_open()
} }
fn load(&mut self, module_data: &[u8]) -> Result<()> { fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.stream = None; self.stream = None;
self.tx.send(None)?; self.instance = None;
let mut store = wasmtime::Store::new(&self.engine, ()); let mut store = wasmtime::Store::new(&self.engine, ());
store.set_epoch_deadline(60); store.set_epoch_deadline(60);
@@ -118,7 +93,7 @@ impl super::Runtime for MicroW8 {
linker.define("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")?;
let platform_data = include_bytes!("../platform/bin/platform.uw8"); let platform_data = include_bytes!("../platform/bin/platform.uw8");
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data); memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
@@ -156,8 +131,12 @@ impl super::Runtime for MicroW8 {
} }
let instance = linker.instantiate(&mut store, &module)?; let instance = linker.instantiate(&mut store, &module)?;
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; let end_frame = platform_instance.get_typed_func::<(), ()>(&mut store, "endFrame")?;
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok(); let update = instance.get_typed_func::<(), ()>(&mut store, "upd").ok();
if let Some(start) = instance.get_typed_func::<(), ()>(&mut store, "start").ok() {
start.call(&mut store, ())?;
}
let (sound_tx, stream) = if self.disable_audio { let (sound_tx, stream) = if self.disable_audio {
(None, None) (None, None)
@@ -174,7 +153,7 @@ impl super::Runtime for MicroW8 {
} }
}; };
self.tx.send(Some(UW8Instance { self.instance = Some(UW8Instance {
store, store,
memory, memory,
end_frame, end_frame,
@@ -182,56 +161,38 @@ impl super::Runtime for MicroW8 {
start_time: Instant::now(), start_time: Instant::now(),
watchdog, watchdog,
sound_tx, sound_tx,
}))?; });
self.stream = stream; self.stream = stream;
self.module_data = Some(module_data.into()); self.module_data = Some(module_data.into());
Ok(()) Ok(())
} }
fn run_frame(&mut self) -> Result<()> { fn run_frame(&mut self) -> Result<()> {
if let Ok(event) = self.rx.try_recv() { let input = self.window.begin_frame();
match event {
UIEvent::Error(err) => err, if input.reset {
UIEvent::Reset => {
if let Some(module_data) = self.module_data.take() { if let Some(module_data) = self.module_data.take() {
self.load(&module_data) self.load(&module_data)?;
} else {
Ok(())
}
}
}
} else {
Ok(())
}
} }
} }
struct State {
instance: Option<UW8Instance>,
timeout: u32,
}
impl State {
fn run_frame(
&mut self,
framebuffer: &mut dyn uw8_window::Framebuffer,
gamepad: u32,
) -> Result<Instant> {
let now = Instant::now(); let now = Instant::now();
let mut result = Ok(now); let mut result = Ok(());
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 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 =
result = Ok(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)
};
{ {
let mem = instance.memory.data_mut(&mut instance.store); let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes()); mem[64..68].copy_from_slice(&time.to_le_bytes());
mem[68..72].copy_from_slice(&gamepad.to_le_bytes()); mem[68..72].copy_from_slice(&input.gamepads);
} }
instance.store.set_epoch_deadline(self.timeout as u64); instance.store.set_epoch_deadline(self.timeout as u64);
@@ -255,14 +216,16 @@ impl State {
let framebuffer_mem = &memory[120..(120 + 320 * 240)]; let framebuffer_mem = &memory[120..(120 + 320 * 240)];
let palette_mem = &memory[0x13000..]; let palette_mem = &memory[0x13000..];
framebuffer.update(framebuffer_mem, palette_mem); self.window
.end_frame(framebuffer_mem, palette_mem, next_frame);
if result.is_ok() { if result.is_ok() {
self.instance = Some(instance); self.instance = Some(instance);
} }
} }
Ok(result?) result?;
Ok(())
} }
} }
@@ -356,8 +319,8 @@ fn init_sound(
let instance = linker.instantiate(&mut store, module)?; let instance = linker.instantiate(&mut store, module)?;
let snd = instance let snd = instance
.get_typed_func::<(i32,), f32, _>(&mut store, "snd") .get_typed_func::<(i32,), f32>(&mut store, "snd")
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd"))?; .or_else(|_| platform_instance.get_typed_func::<(i32,), f32>(&mut store, "sndGes"))?;
let host = cpal::default_host(); let host = cpal::default_host();
let device = host let device = host
@@ -385,7 +348,7 @@ fn init_sound(
.ok_or_else(|| anyhow!("Could not find float 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())
.max(config.max_sample_rate()); .min(config.max_sample_rate());
let config = config.with_sample_rate(sample_rate); let config = config.with_sample_rate(sample_rate);
let buffer_size = match *config.buffer_size() { let buffer_size = match *config.buffer_size() {
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default, cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,

View File

@@ -11,6 +11,7 @@ use warp::{http::Response, Filter};
pub struct RunWebServer { pub struct RunWebServer {
cart: Arc<Mutex<Vec<u8>>>, cart: Arc<Mutex<Vec<u8>>>,
tx: broadcast::Sender<()>, tx: broadcast::Sender<()>,
socket_addr: SocketAddr,
} }
impl RunWebServer { impl RunWebServer {
@@ -18,8 +19,13 @@ impl RunWebServer {
let cart = Arc::new(Mutex::new(Vec::new())); let cart = Arc::new(Mutex::new(Vec::new()));
let (tx, _) = broadcast::channel(1); let (tx, _) = broadcast::channel(1);
let socket_addr = "127.0.0.1:3030"
.parse::<SocketAddr>()
.expect("Failed to parse socket address");
let server_cart = cart.clone(); let server_cart = cart.clone();
let server_tx = tx.clone(); let server_tx = tx.clone();
let server_addr = socket_addr.clone();
thread::spawn(move || { thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread() let rt = tokio::runtime::Builder::new_current_thread()
.enable_io() .enable_io()
@@ -47,24 +53,26 @@ impl RunWebServer {
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx))) warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
}); });
let socket_addr = "127.0.0.1:3030" let server_future = warp::serve(html.or(cart).or(events)).bind(server_addr);
.parse::<SocketAddr>()
.expect("Failed to parse socket address");
let server_future = warp::serve(html.or(cart).or(events)).bind(socket_addr);
println!("Point browser at http://{}", socket_addr);
let _ignore_result = webbrowser::open(&format!("http://{}", socket_addr));
server_future.await server_future.await
}); });
}); });
RunWebServer { cart, tx } RunWebServer {
cart,
tx,
socket_addr,
}
} }
} }
impl super::Runtime for RunWebServer { impl super::Runtime for RunWebServer {
fn load(&mut self, module_data: &[u8]) -> Result<()> { fn load(&mut self, module_data: &[u8]) -> Result<()> {
if let Ok(mut lock) = self.cart.lock() { if let Ok(mut lock) = self.cart.lock() {
if lock.is_empty() && !module_data.is_empty() {
println!("Point browser at http://{}", self.socket_addr);
let _ignore_result = webbrowser::open(&format!("http://{}", self.socket_addr));
}
lock.clear(); lock.clear();
lock.extend_from_slice(module_data); lock.extend_from_slice(module_data);
} }

View File

@@ -1,13 +0,0 @@
import "env.memory" memory(4);
import "env.printString" fn print(i32);
export fn upd() {
}
start fn start() {
print(0);
}
data 0 {
"Press " i8(0xe0) " and " i8(0xe1) " to adjust, " i8(0xcc) " to commit." i8(0)
}

BIN
test.wasm

Binary file not shown.

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;

5
test/start_fn.cwa Normal file
View File

@@ -0,0 +1,5 @@
include "../examples/include/microw8-api.cwa"
export fn start() {
printChar('Test');
}

13
test/text_modes.cwa Normal file
View File

@@ -0,0 +1,13 @@
include "../examples/include/microw8-api.cwa"
export fn upd() {
printString(USER_MEM);
}
data USER_MEM {
i8(12, 31, 5, 6) "Text mode"
i8(5, 31, 4, 5) "Graphics mode"
i8(6) "Console output\nSecond line\n"
i8(4, 31, 4, 12) "Back to text mode"
i8(0)
}

136
uw8-tool/Cargo.lock generated
View File

@@ -56,6 +56,21 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.3.3"
@@ -71,6 +86,26 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -83,6 +118,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.112" version = "0.2.112"
@@ -120,10 +161,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pico-args" name = "percent-encoding"
version = "0.4.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@@ -163,6 +210,26 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.44" version = "0.1.44"
@@ -174,6 +241,36 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.8.0" version = "1.8.0"
@@ -188,13 +285,24 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]] [[package]]
name = "upkr" name = "upkr"
version = "0.1.0" version = "0.2.1"
source = "git+https://github.com/exoticorn/upkr.git?rev=d93aec186c9fb91d962c488682a2db125c61306c#d93aec186c9fb91d962c488682a2db125c61306c" source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cdivsufsort", "cdivsufsort",
"pbr", "lexopt",
"pico-args", "thiserror",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
] ]
[[package]] [[package]]
@@ -207,7 +315,7 @@ dependencies = [
"upkr", "upkr",
"walrus", "walrus",
"wasm-encoder", "wasm-encoder",
"wasmparser 0.81.0", "wasmparser 0.99.0",
] ]
[[package]] [[package]]
@@ -244,9 +352,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasm-encoder" name = "wasm-encoder"
version = "0.8.0" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0" checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
dependencies = [ dependencies = [
"leb128", "leb128",
] ]
@@ -259,9 +367,13 @@ checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.81.0" version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
dependencies = [
"indexmap",
"url",
]
[[package]] [[package]]
name = "winapi" name = "winapi"

View File

@@ -6,10 +6,10 @@ 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]
wasmparser = "0.81" wasmparser = "0.99"
wasm-encoder = "0.8" wasm-encoder = "0.22"
walrus = "0.19" walrus = "0.19"
anyhow = "1" anyhow = "1"
pico-args = "0.4" pico-args = "0.5"
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "d93aec186c9fb91d962c488682a2db125c61306c" } upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "080db40d0088bbee2bdf3c5c75288ac7853d6b7a" }
pbr = "1" pbr = "1"

View File

@@ -3,7 +3,7 @@ use std::{collections::HashMap, fs::File, path::Path};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::io::prelude::*; use std::io::prelude::*;
use wasm_encoder::{ use wasm_encoder::{
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection, CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, ImportSection,
Instruction, MemoryType, Module, TypeSection, ValType, Instruction, MemoryType, Module, TypeSection, ValType,
}; };
use ValType::*; use ValType::*;
@@ -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,
); );
@@ -167,6 +167,22 @@ impl BaseModule {
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32)); 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, "playNote", &[I32, I32], None);
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(
@@ -217,13 +233,13 @@ impl BaseModule {
let mut imports = ImportSection::new(); let mut imports = ImportSection::new();
for (module, name, type_) in &self.function_imports { for (module, name, type_) in &self.function_imports {
imports.import(*module, Some(name.as_str()), EntityType::Function(*type_)); imports.import(*module, name.as_str(), EntityType::Function(*type_));
} }
for (module, name, import) in &self.global_imports { for (module, name, import) in &self.global_imports {
imports.import( imports.import(
*module, *module,
Some(name.as_str()), name.as_str(),
EntityType::Global(wasm_encoder::GlobalType { EntityType::Global(wasm_encoder::GlobalType {
val_type: import.type_, val_type: import.type_,
mutable: import.mutable, mutable: import.mutable,
@@ -233,11 +249,12 @@ impl BaseModule {
imports.import( imports.import(
"env", "env",
Some("memory"), "memory",
MemoryType { MemoryType {
minimum: self.memory as u64, minimum: self.memory as u64,
maximum: None, maximum: None,
memory64: false, memory64: false,
shared: false,
}, },
); );
@@ -258,7 +275,7 @@ impl BaseModule {
let mut exports = ExportSection::new(); let mut exports = ExportSection::new();
for (name, fnc) in &self.exports { for (name, fnc) in &self.exports {
exports.export(*name, Export::Function(*fnc)); exports.export(*name, ExportKind::Func, *fnc);
} }
module.section(&exports); module.section(&exports);
@@ -286,7 +303,7 @@ impl BaseModule {
pub fn create_binary(path: &Path) -> Result<()> { pub fn create_binary(path: &Path) -> Result<()> {
let base1 = BaseModule::for_format_version(1)?.to_wasm(); let base1 = BaseModule::for_format_version(1)?.to_wasm();
let data = upkr::pack(&base1, 4, false, None); let data = upkr::pack(&base1, 4, &upkr::Config::default(), None);
File::create(path)?.write_all(&data)?; File::create(path)?.write_all(&data)?;
Ok(()) Ok(())
} }

View File

@@ -10,7 +10,7 @@ use std::{
use wasm_encoder as enc; use wasm_encoder as enc;
use wasmparser::{ use wasmparser::{
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader, BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
ImportSectionEntryType, ImportSectionReader, TableSectionReader, TypeSectionReader, ImportSectionReader, TableSectionReader, TypeRef, TypeSectionReader,
}; };
pub struct PackConfig { pub struct PackConfig {
@@ -63,7 +63,7 @@ pub fn pack(data: &[u8], config: &PackConfig) -> Result<Vec<u8>> {
uw8.extend_from_slice(&upkr::pack( uw8.extend_from_slice(&upkr::pack(
&result[8..], &result[8..],
level, level,
false, &upkr::Config::default(),
Some(&mut |pos| { Some(&mut |pos| {
pb.set(pos as u64); pb.set(pos as u64);
}), }),
@@ -90,7 +90,10 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
let (version, data) = match data[0] { let (version, data) = match data[0] {
0 => return Ok(data), 0 => return Ok(data),
1 => (1, data[1..].to_vec()), 1 => (1, data[1..].to_vec()),
2 => (1, upkr::unpack(&data[1..], false)), 2 => (
1,
upkr::unpack(&data[1..], &upkr::Config::default(), 4 * 1024 * 1024)?,
),
other => bail!("Uknown format version {}", other), other => bail!("Uknown format version {}", other),
}; };
@@ -133,8 +136,8 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
Ok(dest) Ok(dest)
} }
fn to_val_type(type_: &wasmparser::Type) -> Result<ValType> { fn to_val_type(type_: &wasmparser::ValType) -> Result<ValType> {
use wasmparser::Type::*; use wasmparser::ValType::*;
Ok(match *type_ { Ok(match *type_ {
I32 => ValType::I32, I32 => ValType::I32,
I64 => ValType::I64, I64 => ValType::I64,
@@ -144,7 +147,7 @@ fn to_val_type(type_: &wasmparser::Type) -> Result<ValType> {
}) })
} }
fn to_val_type_vec(types: &[wasmparser::Type]) -> Result<Vec<ValType>> { fn to_val_type_vec(types: &[wasmparser::ValType]) -> Result<Vec<ValType>> {
types.into_iter().map(to_val_type).collect() types.into_iter().map(to_val_type).collect()
} }
@@ -202,7 +205,7 @@ impl<'a> ParsedModule<'a> {
import_section = Some(Section::new(range, ImportSection::parse(reader)?)); import_section = Some(Section::new(range, ImportSection::parse(reader)?));
} }
Payload::GlobalSection(reader) => { Payload::GlobalSection(reader) => {
global_section = Some(Section::new(range, reader.get_count())); global_section = Some(Section::new(range, reader.count()));
} }
Payload::FunctionSection(reader) => { Payload::FunctionSection(reader) => {
function_section = Some(Section::new(range, read_function_section(reader)?)); function_section = Some(Section::new(range, read_function_section(reader)?));
@@ -220,17 +223,22 @@ impl<'a> ParsedModule<'a> {
validate_table_section(reader)?; validate_table_section(reader)?;
table_section = Some(Section::new(range, ())); table_section = Some(Section::new(range, ()));
} }
Payload::ElementSection(mut reader) => { Payload::MemorySection(reader) => {
let mut elements = Vec::with_capacity(reader.get_count() as usize); if reader.count() != 0 {
for _ in 0..reader.get_count() { bail!("Found non-empty MemorySection. Memory has to be imported!");
elements.push(Element::parse(reader.read()?)?); }
}
Payload::ElementSection(reader) => {
let mut elements = Vec::with_capacity(reader.count() as usize);
for element in reader {
elements.push(Element::parse(element?)?);
} }
element_section = Some(elements); element_section = Some(elements);
} }
Payload::CodeSectionStart { .. } => (), Payload::CodeSectionStart { .. } => (),
Payload::CodeSectionEntry(body) => function_bodies.push(body), Payload::CodeSectionEntry(body) => function_bodies.push(body),
Payload::CustomSection { .. } => (), Payload::CustomSection { .. } => (),
Payload::End => break, Payload::End(..) => break,
other => bail!("Unsupported section: {:?}", other), other => bail!("Unsupported section: {:?}", other),
} }
@@ -458,7 +466,7 @@ impl<'a> ParsedModule<'a> {
{ {
let mut export_section = enc::ExportSection::new(); let mut export_section = enc::ExportSection::new();
for (name, fnc) in my_exports { for (name, fnc) in my_exports {
export_section.export(&name, enc::Export::Function(fnc)); export_section.export(&name, enc::ExportKind::Func, fnc);
} }
module.section(&export_section); module.section(&export_section);
} }
@@ -481,7 +489,7 @@ impl<'a> ParsedModule<'a> {
} }
element_section.active( element_section.active(
None, None,
&wasm_encoder::Instruction::I32Const(element.start_index as i32), &wasm_encoder::ConstExpr::i32_const(element.start_index as i32),
ValType::FuncRef, ValType::FuncRef,
wasm_encoder::Elements::Functions(&functions), wasm_encoder::Elements::Functions(&functions),
); );
@@ -530,28 +538,27 @@ fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::Funct
for type_def in reader { for type_def in reader {
match type_def? { match type_def? {
wasmparser::TypeDef::Func(fnc) => { wasmparser::Type::Func(fnc) => {
if fnc.returns.len() > 1 { if fnc.results().len() > 1 {
bail!("Multi-value not supported"); bail!("Multi-value not supported");
} }
let params = to_val_type_vec(&fnc.params)?; let params = to_val_type_vec(fnc.params())?;
let result = to_val_type_vec(&fnc.returns)?.into_iter().next(); let result = to_val_type_vec(fnc.results())?.into_iter().next();
function_types.push(FunctionType { params, result }); function_types.push(FunctionType { params, result });
} }
t => bail!("Unsupported type def {:?}", t),
} }
} }
Ok(function_types) Ok(function_types)
} }
fn validate_table_section(mut reader: TableSectionReader) -> Result<()> { fn validate_table_section(reader: TableSectionReader) -> Result<()> {
if reader.get_count() != 1 { if reader.count() != 1 {
bail!("Only up to one table supported"); bail!("Only up to one table supported");
} }
let type_ = reader.read()?; let type_ = reader.into_iter().next().unwrap()?;
if type_.element_type != wasmparser::Type::FuncRef { if type_.element_type != wasmparser::ValType::FuncRef {
bail!("Only one funcref table is supported"); bail!("Only one funcref table is supported");
} }
@@ -585,21 +592,20 @@ impl ImportSection {
for import in reader { for import in reader {
let import = import?; let import = import?;
if let Some(field) = import.field {
match import.ty { match import.ty {
ImportSectionEntryType::Function(type_) => { TypeRef::Func(type_) => {
functions.push(FunctionImport { functions.push(FunctionImport {
module: import.module.to_string(), module: import.module.to_string(),
field: field.to_string(), field: import.name.to_string(),
type_, type_,
}); });
} }
ImportSectionEntryType::Memory(mem) => { TypeRef::Memory(mem) => {
if import.module != "env" || field != "memory" { if import.module != "env" || import.name != "memory" {
bail!( bail!(
"Wrong name of memory import {}.{}, should be env.memory", "Wrong name of memory import {}.{}, should be env.memory",
import.module, import.module,
field import.name
); );
} }
if mem.memory64 || mem.shared { if mem.memory64 || mem.shared {
@@ -607,10 +613,10 @@ impl ImportSection {
} }
memory = mem.maximum.unwrap_or(mem.initial) as u32; memory = mem.maximum.unwrap_or(mem.initial) as u32;
} }
ImportSectionEntryType::Global(glbl) => { TypeRef::Global(glbl) => {
globals.push(GlobalImport { globals.push(GlobalImport {
module: import.module.to_string(), module: import.module.to_string(),
field: field.to_string(), field: import.name.to_string(),
type_: GlobalType { type_: GlobalType {
type_: to_val_type(&glbl.content_type)?, type_: to_val_type(&glbl.content_type)?,
mutable: glbl.mutable, mutable: glbl.mutable,
@@ -619,12 +625,6 @@ impl ImportSection {
} }
_ => bail!("Unsupported import item {:?}", import.ty), _ => bail!("Unsupported import item {:?}", import.ty),
} }
} else {
bail!(
"Found import without field, only module '{}'",
import.module
);
}
} }
Ok(ImportSection { Ok(ImportSection {
@@ -643,17 +643,17 @@ struct Element {
impl Element { impl Element {
fn parse(element: wasmparser::Element) -> Result<Element> { fn parse(element: wasmparser::Element) -> Result<Element> {
if element.ty != wasmparser::Type::FuncRef { match element.items {
bail!("Table element type is not FuncRef"); wasmparser::ElementItems::Functions(funcs_reader) => {
}
let start_index = if let wasmparser::ElementKind::Active { let start_index = if let wasmparser::ElementKind::Active {
init_expr, offset_expr,
table_index: 0, table_index: 0,
} = element.kind } = element.kind
{ {
let mut init_reader = init_expr.get_operators_reader(); let mut init_reader = offset_expr.get_operators_reader();
if let wasmparser::Operator::I32Const { value: start_index } = init_reader.read()? { if let wasmparser::Operator::I32Const { value: start_index } =
init_reader.read()?
{
start_index as u32 start_index as u32
} else { } else {
bail!("Table element start index is not a integer constant"); bail!("Table element start index is not a integer constant");
@@ -662,15 +662,9 @@ impl Element {
bail!("Unsupported table element kind"); bail!("Unsupported table element kind");
}; };
let mut items_reader = element.items.get_items_reader()?; let mut functions = Vec::with_capacity(funcs_reader.count() as usize);
for index in funcs_reader {
let mut functions = Vec::with_capacity(items_reader.get_count() as usize); functions.push(index?);
for _ in 0..items_reader.get_count() {
if let wasmparser::ElementItem::Func(index) = items_reader.read()? {
functions.push(index);
} else {
bail!("Table element item is not a function");
}
} }
Ok(Element { Ok(Element {
@@ -678,6 +672,9 @@ impl Element {
functions, functions,
}) })
} }
_ => bail!("Table element type is not FuncRef"),
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -707,8 +704,8 @@ fn read_export_section(reader: ExportSectionReader) -> Result<Vec<(String, u32)>
for export in reader { for export in reader {
let export = export?; let export = export?;
match export.kind { match export.kind {
ExternalKind::Function => { ExternalKind::Func => {
function_exports.push((export.field.to_string(), export.index)); function_exports.push((export.name.to_string(), export.index));
} }
_ => (), // just ignore all other kinds since MicroW8 doesn't expect any exports other than functions _ => (), // just ignore all other kinds since MicroW8 doesn't expect any exports other than functions
} }
@@ -729,13 +726,11 @@ fn remap_function(
} }
let mut function = enc::Function::new(locals); let mut function = enc::Function::new(locals);
let block_type = |ty: wasmparser::TypeOrFuncType| -> Result<enc::BlockType> { let block_type = |ty: wasmparser::BlockType| -> Result<enc::BlockType> {
Ok(match ty { Ok(match ty {
wasmparser::TypeOrFuncType::Type(wasmparser::Type::EmptyBlockType) => { wasmparser::BlockType::Empty => enc::BlockType::Empty,
enc::BlockType::Empty wasmparser::BlockType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
} wasmparser::BlockType::FuncType(ty) => enc::BlockType::FunctionType(
wasmparser::TypeOrFuncType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
wasmparser::TypeOrFuncType::FuncType(ty) => enc::BlockType::FunctionType(
*type_map *type_map
.get(&ty) .get(&ty)
.ok_or_else(|| anyhow!("Function type index out of range: {}", ty))?, .ok_or_else(|| anyhow!("Function type index out of range: {}", ty))?,
@@ -749,7 +744,7 @@ fn remap_function(
.ok_or_else(|| anyhow!("Global index out of range: {}", idx))?) .ok_or_else(|| anyhow!("Global index out of range: {}", idx))?)
}; };
fn mem(m: wasmparser::MemoryImmediate) -> enc::MemArg { fn mem(m: wasmparser::MemArg) -> enc::MemArg {
enc::MemArg { enc::MemArg {
offset: m.offset, offset: m.offset,
align: m.align as u32, align: m.align as u32,
@@ -764,24 +759,31 @@ fn remap_function(
function.instruction(&match op? { function.instruction(&match op? {
De::Unreachable => En::Unreachable, De::Unreachable => En::Unreachable,
De::Nop => En::Nop, De::Nop => En::Nop,
De::Block { ty } => En::Block(block_type(ty)?), De::Block { blockty } => En::Block(block_type(blockty)?),
De::Loop { ty } => En::Loop(block_type(ty)?), De::Loop { blockty } => En::Loop(block_type(blockty)?),
De::If { ty } => En::If(block_type(ty)?), De::If { blockty } => En::If(block_type(blockty)?),
De::Else => En::Else, De::Else => En::Else,
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(), De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
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
.get(&function_index) .get(&function_index)
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?, .ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
), ),
De::CallIndirect { index, table_index } => En::CallIndirect { De::CallIndirect {
type_index,
table_index,
table_byte: _, // what is this supposed to be?
} => En::CallIndirect {
ty: *type_map ty: *type_map
.get(&index) .get(&type_index)
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?, .ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
table: table_index, table: table_index,
}, },
@@ -801,16 +803,16 @@ fn remap_function(
De::I64Load { memarg } => En::I64Load(mem(memarg)), De::I64Load { memarg } => En::I64Load(mem(memarg)),
De::F32Load { memarg } => En::F32Load(mem(memarg)), De::F32Load { memarg } => En::F32Load(mem(memarg)),
De::F64Load { memarg } => En::F64Load(mem(memarg)), De::F64Load { memarg } => En::F64Load(mem(memarg)),
De::I32Load8S { memarg } => En::I32Load8_S(mem(memarg)), De::I32Load8S { memarg } => En::I32Load8S(mem(memarg)),
De::I32Load8U { memarg } => En::I32Load8_U(mem(memarg)), De::I32Load8U { memarg } => En::I32Load8U(mem(memarg)),
De::I32Load16S { memarg } => En::I32Load16_S(mem(memarg)), De::I32Load16S { memarg } => En::I32Load16S(mem(memarg)),
De::I32Load16U { memarg } => En::I32Load16_U(mem(memarg)), De::I32Load16U { memarg } => En::I32Load16U(mem(memarg)),
De::I64Load8S { memarg } => En::I64Load8_S(mem(memarg)), De::I64Load8S { memarg } => En::I64Load8S(mem(memarg)),
De::I64Load8U { memarg } => En::I64Load8_U(mem(memarg)), De::I64Load8U { memarg } => En::I64Load8U(mem(memarg)),
De::I64Load16S { memarg } => En::I64Load16_S(mem(memarg)), De::I64Load16S { memarg } => En::I64Load16S(mem(memarg)),
De::I64Load16U { memarg } => En::I64Load16_U(mem(memarg)), De::I64Load16U { memarg } => En::I64Load16U(mem(memarg)),
De::I64Load32S { memarg } => En::I64Load32_S(mem(memarg)), De::I64Load32S { memarg } => En::I64Load32S(mem(memarg)),
De::I64Load32U { memarg } => En::I64Load32_U(mem(memarg)), De::I64Load32U { memarg } => En::I64Load32U(mem(memarg)),
De::I32Store { memarg } => En::I32Store(mem(memarg)), De::I32Store { memarg } => En::I32Store(mem(memarg)),
De::I64Store { memarg } => En::I64Store(mem(memarg)), De::I64Store { memarg } => En::I64Store(mem(memarg)),
De::F32Store { memarg } => En::F32Store(mem(memarg)), De::F32Store { memarg } => En::F32Store(mem(memarg)),
@@ -829,7 +831,7 @@ fn remap_function(
De::RefNull { .. } | De::RefIsNull { .. } | De::RefFunc { .. } => todo!(), De::RefNull { .. } | De::RefIsNull { .. } | De::RefFunc { .. } => todo!(),
De::I32Eqz => En::I32Eqz, De::I32Eqz => En::I32Eqz,
De::I32Eq => En::I32Eq, De::I32Eq => En::I32Eq,
De::I32Ne => En::I32Neq, De::I32Ne => En::I32Ne,
De::I32LtS => En::I32LtS, De::I32LtS => En::I32LtS,
De::I32LtU => En::I32LtU, De::I32LtU => En::I32LtU,
De::I32GtS => En::I32GtS, De::I32GtS => En::I32GtS,
@@ -840,7 +842,7 @@ fn remap_function(
De::I32GeU => En::I32GeU, De::I32GeU => En::I32GeU,
De::I64Eqz => En::I64Eqz, De::I64Eqz => En::I64Eqz,
De::I64Eq => En::I64Eq, De::I64Eq => En::I64Eq,
De::I64Ne => En::I64Neq, De::I64Ne => En::I64Ne,
De::I64LtS => En::I64LtS, De::I64LtS => En::I64LtS,
De::I64LtU => En::I64LtU, De::I64LtU => En::I64LtU,
De::I64GtS => En::I64GtS, De::I64GtS => En::I64GtS,
@@ -850,13 +852,13 @@ fn remap_function(
De::I64GeS => En::I64GeS, De::I64GeS => En::I64GeS,
De::I64GeU => En::I64GeU, De::I64GeU => En::I64GeU,
De::F32Eq => En::F32Eq, De::F32Eq => En::F32Eq,
De::F32Ne => En::F32Neq, De::F32Ne => En::F32Ne,
De::F32Lt => En::F32Lt, De::F32Lt => En::F32Lt,
De::F32Gt => En::F32Gt, De::F32Gt => En::F32Gt,
De::F32Le => En::F32Le, De::F32Le => En::F32Le,
De::F32Ge => En::F32Ge, De::F32Ge => En::F32Ge,
De::F64Eq => En::F64Eq, De::F64Eq => En::F64Eq,
De::F64Ne => En::F64Neq, De::F64Ne => En::F64Ne,
De::F64Lt => En::F64Lt, De::F64Lt => En::F64Lt,
De::F64Gt => En::F64Gt, De::F64Gt => En::F64Gt,
De::F64Le => En::F64Le, De::F64Le => En::F64Le,
@@ -963,7 +965,7 @@ fn remap_function(
De::I64TruncSatF32U => En::I64TruncSatF32U, De::I64TruncSatF32U => En::I64TruncSatF32U,
De::I64TruncSatF64S => En::I64TruncSatF64S, De::I64TruncSatF64S => En::I64TruncSatF64S,
De::I64TruncSatF64U => En::I64TruncSatF64U, De::I64TruncSatF64U => En::I64TruncSatF64U,
De::MemoryCopy { src, dst } => En::MemoryCopy { src, dst }, De::MemoryCopy { src_mem, dst_mem } => En::MemoryCopy { src_mem, dst_mem },
De::MemoryFill { mem } => En::MemoryFill(mem), De::MemoryFill { mem } => En::MemoryFill(mem),
other => bail!("Unsupported instruction {:?}", other), other => bail!("Unsupported instruction {:?}", other),
}); });

847
uw8-window/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +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.26.1" winit = "0.27.5"
env_logger = "0.9" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.13.1" pico-args = "0.5"
pollster = "0.2" wgpu = "0.15"
bytemuck = { version = "1.4", features = [ "derive" ] } pollster = "0.2.5"
bytemuck = { version = "1.13", features = [ "derive" ] }
anyhow = "1" anyhow = "1"
minifb = { version = "0.23.0", default-features = false, features = ["x11"] } minifb = { version = "0.23.0", default-features = false, features = ["x11"] }
winapi = "0.3.9" winapi = { version = "0.3.9", features = [ "timeapi" ] }

View File

@@ -1,7 +1,8 @@
use std::time::Instant; use std::time::Instant;
use crate::Framebuffer; use crate::{Input, WindowImpl};
use minifb::{Key, Window, WindowOptions}; use anyhow::Result;
use minifb::{Key, WindowOptions};
static GAMEPAD_KEYS: &[Key] = &[ static GAMEPAD_KEYS: &[Key] = &[
Key::Up, Key::Up,
@@ -14,13 +15,19 @@ static GAMEPAD_KEYS: &[Key] = &[
Key::S, Key::S,
]; ];
pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! { pub struct Window {
window: minifb::Window,
buffer: Vec<u32>,
}
impl Window {
pub fn new() -> Result<Window> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
unsafe { unsafe {
winapi::um::timeapi::timeBeginPeriod(1); winapi::um::timeapi::timeBeginPeriod(1);
} }
let mut buffer: Vec<u32> = vec![0; 320 * 240]; let buffer: Vec<u32> = vec![0; 320 * 240];
let options = WindowOptions { let options = WindowOptions {
scale: minifb::Scale::X2, scale: minifb::Scale::X2,
@@ -28,44 +35,33 @@ pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant
resize: true, resize: true,
..Default::default() ..Default::default()
}; };
let mut window = Window::new("MicroW8", 320, 240, options).unwrap(); let window = minifb::Window::new("MicroW8", 320, 240, options).unwrap();
let mut next_frame = Instant::now(); Ok(Window { window, buffer })
while window.is_open() && !window.is_key_down(Key::Escape) { }
if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) {
std::thread::sleep(sleep);
} }
let mut gamepad = 0; impl WindowImpl for Window {
for key in window.get_keys() { fn begin_frame(&mut self) -> Input {
let mut gamepads = [0u8; 4];
for key in self.window.get_keys() {
if let Some(index) = GAMEPAD_KEYS if let Some(index) = GAMEPAD_KEYS
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, &k)| k == key) .find(|(_, &k)| k == key)
.map(|(i, _)| i) .map(|(i, _)| i)
{ {
gamepad |= 1 << index; gamepads[0] |= 1 << index;
} }
} }
next_frame = update( Input {
&mut CpuFramebuffer { gamepads,
buffer: &mut buffer, reset: self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
},
gamepad,
window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
);
window.update_with_buffer(&buffer, 320, 240).unwrap();
} }
std::process::exit(0);
} }
struct CpuFramebuffer<'a> { fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
buffer: &'a mut Vec<u32>,
}
impl<'a> Framebuffer for CpuFramebuffer<'a> {
fn update(&mut self, framebuffer: &[u8], palette: &[u8]) {
for (i, &color_index) in framebuffer.iter().enumerate() { for (i, &color_index) in framebuffer.iter().enumerate() {
let offset = color_index as usize * 4; let offset = color_index as usize * 4;
self.buffer[i] = 0xff000000 self.buffer[i] = 0xff000000
@@ -73,5 +69,15 @@ impl<'a> Framebuffer for CpuFramebuffer<'a> {
| ((palette[offset + 1] as u32) << 8) | ((palette[offset + 1] as u32) << 8)
| palette[offset + 2] as u32; | palette[offset + 2] as u32;
} }
self.window
.update_with_buffer(&self.buffer, 320, 240)
.unwrap();
if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) {
std::thread::sleep(sleep);
}
}
fn is_open(&self) -> bool {
self.window.is_open() && !self.window.is_key_down(Key::Escape)
} }
} }

View File

@@ -112,7 +112,7 @@ impl CrtFilter {
} }
impl Filter for CrtFilter { impl Filter for CrtFilter {
fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) { fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
let uniforms = Uniforms { let uniforms = Uniforms {
texture_scale: texture_scale_from_resolution(new_size), texture_scale: texture_scale_from_resolution(new_size),
}; };

View File

@@ -36,36 +36,36 @@ fn sample_pixel(coords: vec2<i32>, offset: vec4<f32>) -> vec3<f32> {
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let pixel = floor(in.tex_coords); let pixelf = floor(in.tex_coords);
let o = vec2<f32>(0.5) - (in.tex_coords - pixel); let o = vec2<f32>(0.5) - (in.tex_coords - pixelf);
let pixel = vec2<i32>(pixel); 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_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_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)); var 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)); var 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_x2 = max(abs(offset_x + vec4<f32>(1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
let offset_x0 = offset_x0 * offset_x0; offset_x0 = offset_x0 * offset_x0;
let offset_x1 = offset_x1 * offset_x1; offset_x1 = offset_x1 * offset_x1;
let offset_x2 = offset_x2 * offset_x2; offset_x2 = offset_x2 * offset_x2;
let offset_yr = offset_y + vec4<f32>(-1.0); var offset_yr = offset_y + vec4<f32>(-1.0);
let offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr; offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
var acc = sample_pixel(pixel + vec2<i32>(-1, -1), offset_x0 + 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>(0, -1), offset_x1 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(1, -1), offset_x2 + 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 + vec2<i32>(-1, 0), offset_x0 + offset_yr);
acc = acc + sample_pixel(pixel, offset_x1 + offset_yr); acc = acc + sample_pixel(pixel, offset_x1 + offset_yr);
acc = acc + sample_pixel(pixel + vec2<i32>(1, 0), offset_x2 + offset_yr); acc = acc + sample_pixel(pixel + vec2<i32>(1, 0), offset_x2 + offset_yr);
let offset_yr = offset_y + vec4<f32>(1.0); offset_yr = offset_y + vec4<f32>(1.0);
let offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr; 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>(-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>(0, 1), offset_x1 + offset_yr);

View File

@@ -15,6 +15,7 @@ impl FastCrtFilter {
screen: &wgpu::TextureView, screen: &wgpu::TextureView,
resolution: PhysicalSize<u32>, resolution: PhysicalSize<u32>,
surface_format: wgpu::TextureFormat, surface_format: wgpu::TextureFormat,
chromatic: bool,
) -> FastCrtFilter { ) -> FastCrtFilter {
let uniforms = Uniforms { let uniforms = Uniforms {
texture_scale: texture_scale_from_resolution(resolution), texture_scale: texture_scale_from_resolution(resolution),
@@ -104,7 +105,11 @@ impl FastCrtFilter {
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &shader, module: &shader,
entry_point: "fs_main", entry_point: if chromatic {
"fs_main_chromatic"
} else {
"fs_main"
},
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: surface_format, format: surface_format,
blend: None, blend: None,
@@ -126,7 +131,7 @@ impl FastCrtFilter {
} }
impl Filter for FastCrtFilter { impl Filter for FastCrtFilter {
fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) { fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
let uniforms = Uniforms { let uniforms = Uniforms {
texture_scale: texture_scale_from_resolution(new_size), texture_scale: texture_scale_from_resolution(new_size),
}; };

View File

@@ -34,10 +34,9 @@ fn col_factor(offset: f32) -> f32 {
return 1.0 / (1.0 + offset * offset * 16.0); return 1.0 / (1.0 + offset * offset * 16.0);
} }
@fragment fn sample_screen(tex_coords: vec2<f32>) -> vec4<f32> {
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { let base = round(tex_coords) - vec2<f32>(0.5);
let base = round(in.tex_coords) - vec2<f32>(0.5); let frac = tex_coords - base;
let frac = in.tex_coords - base;
let top_factor = row_factor(frac.y); let top_factor = row_factor(frac.y);
let bottom_factor = row_factor(frac.y - 1.0); let bottom_factor = row_factor(frac.y - 1.0);
@@ -52,3 +51,16 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(screen_texture, linear_sampler, vec2<f32>(u, v) / vec2<f32>(320.0, 240.0)) * (top_factor + bottom_factor) * (left_factor + right_factor) * 1.1; return textureSample(screen_texture, linear_sampler, vec2<f32>(u, v) / vec2<f32>(320.0, 240.0)) * (top_factor + bottom_factor) * (left_factor + right_factor) * 1.1;
} }
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return sample_screen(in.tex_coords);
}
@fragment
fn fs_main_chromatic(in: VertexOutput) -> @location(0) vec4<f32> {
let r = sample_screen(in.tex_coords + vec2<f32>(0.2, 0.2)).r;
let g = sample_screen(in.tex_coords + vec2<f32>(0.07, -0.27)).g;
let b = sample_screen(in.tex_coords + vec2<f32>(-0.27, 0.07)).b;
return vec4<f32>(r, g, b, 1.0);
}

View File

@@ -1,4 +1,4 @@
use crate::Framebuffer; use crate::{Input, WindowConfig, WindowImpl};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::{num::NonZeroU32, time::Instant}; use std::{num::NonZeroU32, time::Instant};
@@ -9,12 +9,7 @@ use winit::{
window::{Fullscreen, WindowBuilder}, window::{Fullscreen, WindowBuilder},
}; };
#[cfg(target_os = "macos")] use winit::platform::run_return::EventLoopExtRunReturn;
use winit::platform::macos::EventLoopExtMacOS;
#[cfg(target_os = "linux")]
use winit::platform::unix::EventLoopExtUnix;
#[cfg(target_os = "windows")]
use winit::platform::windows::EventLoopExtWindows;
mod crt; mod crt;
mod fast_crt; mod fast_crt;
@@ -25,29 +20,41 @@ use fast_crt::FastCrtFilter;
use square::SquareFilter; use square::SquareFilter;
pub struct Window { pub struct Window {
event_loop: EventLoop<()>, _instance: wgpu::Instance,
window: winit::window::Window,
instance: wgpu::Instance,
surface: wgpu::Surface, surface: wgpu::Surface,
adapter: wgpu::Adapter, _adapter: wgpu::Adapter,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
palette_screen_mode: PaletteScreenMode,
surface_config: wgpu::SurfaceConfiguration,
filter: Box<dyn Filter>,
event_loop: EventLoop<()>,
window: winit::window::Window,
gamepads: [u8; 4],
next_frame: Instant,
is_fullscreen: bool,
is_open: bool,
} }
impl Window { impl Window {
pub fn new() -> Result<Window> { pub fn new(window_config: WindowConfig) -> Result<Window> {
async fn create() -> Result<Window> { async fn create(window_config: WindowConfig) -> Result<Window> {
let event_loop = EventLoop::new_any_thread(); 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(640u32, 480))
.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 {
Some(Fullscreen::Borderless(None))
} else {
None
})
.build(&event_loop)?; .build(&event_loop)?;
window.set_cursor_visible(false); window.set_cursor_visible(false);
let instance = wgpu::Instance::new(wgpu::Backends::all()); let instance = wgpu::Instance::new(Default::default());
let surface = unsafe { instance.create_surface(&window) }; 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,
@@ -61,70 +68,65 @@ impl Window {
.request_device(&wgpu::DeviceDescriptor::default(), None) .request_device(&wgpu::DeviceDescriptor::default(), None)
.await?; .await?;
Ok(Window {
event_loop,
window,
instance,
surface,
adapter,
device,
queue,
})
}
pollster::block_on(create())
}
pub fn run(
self,
mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>,
) -> ! {
let Window {
event_loop,
window,
instance,
surface,
adapter,
device,
queue,
} = self;
let palette_screen_mode = PaletteScreenMode::new(&device); let palette_screen_mode = PaletteScreenMode::new(&device);
let mut surface_config = wgpu::SurfaceConfiguration { 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, present_mode: wgpu::PresentMode::AutoNoVsync,
..surface.get_default_config(&adapter, window.inner_size().width, window.inner_size().height).expect("Surface incompatible with adapter")
}; };
let mut filter: Box<dyn Filter> = Box::new(CrtFilter::new( let filter: Box<dyn Filter> = create_filter(
&device, &device,
&palette_screen_mode.screen_view, &palette_screen_mode.screen_view,
window.inner_size(), window.inner_size(),
surface_config.format, surface_config.format,
)); window_config.filter,
);
surface.configure(&device, &surface_config); surface.configure(&device, &surface_config);
Ok(Window {
event_loop,
window,
_instance: instance,
surface,
_adapter: adapter,
device,
queue,
palette_screen_mode,
surface_config,
filter,
gamepads: [0; 4],
next_frame: Instant::now(),
is_fullscreen: window_config.fullscreen,
is_open: true,
})
}
pollster::block_on(create(window_config))
}
}
impl WindowImpl for Window {
fn begin_frame(&mut self) -> Input {
let mut reset = false; let mut reset = false;
let mut gamepad = 0; self.event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::WaitUntil(self.next_frame);
event_loop.run(move |event, _, control_flow| { let mut new_filter = None;
let _ = (&window, &instance, &surface, &adapter, &device);
match event { match event {
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
surface_config.width = new_size.width; self.surface_config.width = new_size.width;
surface_config.height = new_size.height; self.surface_config.height = new_size.height;
surface.configure(&device, &surface_config); self.surface.configure(&self.device, &self.surface_config);
filter.resize(&queue, new_size); self.filter.resize(&self.queue, new_size);
}
WindowEvent::CloseRequested => {
self.is_open = false;
*control_flow = ControlFlow::Exit;
} }
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput { input, .. } => { WindowEvent::KeyboardInput { input, .. } => {
fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 { fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
match input.scancode { match input.scancode {
44 => 16, 44 => 16,
45 => 32, 45 => 32,
@@ -141,78 +143,79 @@ impl Window {
} }
if input.state == winit::event::ElementState::Pressed { if input.state == winit::event::ElementState::Pressed {
match input.virtual_keycode { match input.virtual_keycode {
Some(VirtualKeyCode::Escape) => *control_flow = ControlFlow::Exit, Some(VirtualKeyCode::Escape) => {
self.is_open = false;
*control_flow = ControlFlow::Exit;
}
Some(VirtualKeyCode::F) => { Some(VirtualKeyCode::F) => {
window.set_fullscreen(if window.fullscreen().is_some() { let fullscreen = if self.window.fullscreen().is_some() {
None None
} else { } else {
Some(Fullscreen::Borderless(None)) Some(Fullscreen::Borderless(None))
}); };
self.is_fullscreen = fullscreen.is_some();
self.window.set_fullscreen(fullscreen);
} }
Some(VirtualKeyCode::R) => reset = true, Some(VirtualKeyCode::R) => reset = true,
Some(VirtualKeyCode::Key1) => { Some(VirtualKeyCode::Key1) => new_filter = Some(1),
filter = Box::new(SquareFilter::new( Some(VirtualKeyCode::Key2) => new_filter = Some(2),
&device, Some(VirtualKeyCode::Key3) => new_filter = Some(3),
&palette_screen_mode.screen_view, Some(VirtualKeyCode::Key4) => new_filter = Some(4),
window.inner_size(), Some(VirtualKeyCode::Key5) => new_filter = Some(5),
surface_config.format,
))
}
Some(VirtualKeyCode::Key2) => {
filter = Box::new(FastCrtFilter::new(
&device,
&palette_screen_mode.screen_view,
window.inner_size(),
surface_config.format,
))
}
Some(VirtualKeyCode::Key3) => {
filter = Box::new(CrtFilter::new(
&device,
&palette_screen_mode.screen_view,
window.inner_size(),
surface_config.format,
))
}
_ => (), _ => (),
} }
gamepad |= gamepad_button(&input); self.gamepads[0] |= gamepad_button(&input);
} else { } else {
gamepad &= !gamepad_button(&input); self.gamepads[0] &= !gamepad_button(&input);
} }
} }
_ => (), _ => (),
}, },
Event::MainEventsCleared => { Event::RedrawEventsCleared => {
if let ControlFlow::WaitUntil(t) = *control_flow { if Instant::now() >= self.next_frame
if Instant::now() < t { // workaround needed on Wayland until the next winit release
return; && self.window.fullscreen().is_some() == self.is_fullscreen
{
*control_flow = ControlFlow::Exit
} }
} }
let next_frame = update( _ => (),
&mut GpuFramebuffer { }
queue: &queue, if let Some(new_filter) = new_filter {
framebuffer: &palette_screen_mode, self.filter = create_filter(
}, &self.device,
gamepad, &self.palette_screen_mode.screen_view,
reset, self.window.inner_size(),
self.surface_config.format,
new_filter,
); );
reset = false; }
*control_flow = ControlFlow::WaitUntil(next_frame); });
Input {
gamepads: self.gamepads,
reset,
}
}
let output = surface.get_current_texture().unwrap(); fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
self.next_frame = next_frame;
self.palette_screen_mode
.write_framebuffer(&self.queue, framebuffer);
self.palette_screen_mode.write_palette(&self.queue, palette);
let output = self.surface.get_current_texture().unwrap();
let view = output let view = output
.texture .texture
.create_view(&wgpu::TextureViewDescriptor::default()); .create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
palette_screen_mode.resolve_screen(&mut encoder); self.palette_screen_mode.resolve_screen(&mut encoder);
{ {
let mut render_pass = let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None, label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view, view: &view,
@@ -230,35 +233,105 @@ impl Window {
depth_stencil_attachment: None, depth_stencil_attachment: None,
}); });
filter.render(&mut render_pass); self.filter.render(&mut render_pass);
} }
queue.submit(std::iter::once(encoder.finish())); self.queue.submit(std::iter::once(encoder.finish()));
output.present(); output.present();
} }
_ => (),
} fn is_open(&self) -> bool {
}); self.is_open
} }
} }
struct GpuFramebuffer<'a> { fn create_filter(
framebuffer: &'a PaletteScreenMode, device: &wgpu::Device,
queue: &'a wgpu::Queue, screen_texture: &wgpu::TextureView,
} window_size: PhysicalSize<u32>,
surface_format: wgpu::TextureFormat,
impl<'a> Framebuffer for GpuFramebuffer<'a> { filter: u32,
fn update(&mut self, pixels: &[u8], palette: &[u8]) { ) -> Box<dyn Filter> {
self.framebuffer.write_framebuffer(self.queue, pixels); match filter {
self.framebuffer.write_palette(self.queue, palette); 1 => Box::new(SquareFilter::new(
device,
screen_texture,
window_size,
surface_format,
)),
2 => Box::new(FastCrtFilter::new(
device,
screen_texture,
window_size,
surface_format,
false,
)),
3 => Box::new(CrtFilter::new(
device,
screen_texture,
window_size,
surface_format,
)),
4 => Box::new(FastCrtFilter::new(
device,
screen_texture,
window_size,
surface_format,
true,
)),
_ => Box::new(AutoCrtFilter::new(
device,
screen_texture,
window_size,
surface_format,
)),
} }
} }
trait Filter { trait Filter {
fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>); fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>);
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>); fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>);
} }
struct AutoCrtFilter {
small: CrtFilter,
large: FastCrtFilter,
resolution: PhysicalSize<u32>,
}
impl AutoCrtFilter {
fn new(
device: &wgpu::Device,
screen: &wgpu::TextureView,
resolution: PhysicalSize<u32>,
surface_format: wgpu::TextureFormat,
) -> AutoCrtFilter {
let small = CrtFilter::new(device, screen, resolution, surface_format);
let large = FastCrtFilter::new(device, screen, resolution, surface_format, true);
AutoCrtFilter {
small,
large,
resolution,
}
}
}
impl Filter for AutoCrtFilter {
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
self.small.resize(queue, new_size);
self.large.resize(queue, new_size);
self.resolution = new_size;
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
if self.resolution.width < 960 || self.resolution.height < 720 {
self.small.render(render_pass);
} else {
self.large.render(render_pass);
}
}
}
struct PaletteScreenMode { struct PaletteScreenMode {
framebuffer: wgpu::Texture, framebuffer: wgpu::Texture,
palette: wgpu::Texture, palette: wgpu::Texture,
@@ -281,6 +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: &[]
}); });
let palette_texture = device.create_texture(&wgpu::TextureDescriptor { let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -295,6 +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: &[]
}); });
let screen_texture = device.create_texture(&wgpu::TextureDescriptor { let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -309,6 +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: &[]
}); });
let framebuffer_texture_view = let framebuffer_texture_view =

View File

@@ -126,7 +126,7 @@ impl SquareFilter {
} }
impl Filter for SquareFilter { impl Filter for SquareFilter {
fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) { fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
let uniforms = Uniforms { let uniforms = Uniforms {
texture_scale: texture_scale_from_resolution(new_size), texture_scale: texture_scale_from_resolution(new_size),
}; };

View File

@@ -1,24 +1,117 @@
use anyhow::Result;
use std::time::Instant; use std::time::Instant;
mod cpu; mod cpu;
mod gpu; mod gpu;
pub fn run<F: 'static + FnMut(&mut dyn Framebuffer, u32, bool) -> Instant>( pub struct Window {
gpu: bool, inner: Box<dyn WindowImpl>,
update: F, fps_counter: Option<FpsCounter>,
) -> ! { }
if gpu {
match gpu::Window::new() { struct FpsCounter {
Ok(window) => window.run(Box::new(update)), 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 {
inner: Box::new(window),
fps_counter,
})
}
Err(err) => eprintln!( Err(err) => eprintln!(
"Failed to create gpu window: {}\nFalling back to cpu window", "Failed to create gpu window: {}\nFalling back tp cpu window",
err err
), ),
} }
} }
cpu::run(Box::new(update)); cpu::Window::new().map(|window| Window {
inner: Box::new(window),
fps_counter,
})
} }
pub trait Framebuffer { pub fn begin_frame(&mut self) -> Input {
fn update(&mut self, pixels: &[u8], palette: &[u8]); self.inner.begin_frame()
}
pub fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
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.inner.is_open()
}
}
#[derive(Debug)]
pub struct WindowConfig {
enable_gpu: bool,
filter: u32,
fullscreen: bool,
fps_counter: bool,
}
impl Default for WindowConfig {
fn default() -> WindowConfig {
WindowConfig {
enable_gpu: true,
filter: 5,
fullscreen: false,
fps_counter: false,
}
}
}
impl WindowConfig {
pub fn parse_arguments(&mut self, args: &mut pico_args::Arguments) {
self.enable_gpu = !args.contains("--no-gpu");
if let Some(filter) = args.opt_value_from_str::<_, String>("--filter").unwrap() {
self.filter = match filter.as_str() {
"1" | "nearest" => 1,
"2" | "fast_crt" => 2,
"3" | "ss_crt" => 3,
"4" | "chromatic" => 4,
"5" | "auto_crt" => 5,
o => {
println!("Unknown --filter '{}'", o);
std::process::exit(1);
}
}
}
self.fullscreen = args.contains("--fullscreen");
self.fps_counter = args.contains("--fps");
}
}
pub struct Input {
pub gamepads: [u8; 4],
pub reset: bool,
}
trait WindowImpl {
fn begin_frame(&mut self) -> Input;
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant);
fn is_open(&self) -> bool;
} }

View File

@@ -1,10 +1,13 @@
use std::time::Instant; use std::time::Instant;
use uw8_window::WindowConfig;
fn main() { fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let mut args = pico_args::Arguments::from_env();
let mut framebuffer = vec![0u8; 320 * 240]; let mut framebuffer = vec![0u8; 320 * 240];
let start_time = Instant::now(); let mut start_time = Instant::now();
let mut palette = vec![0u32; 256]; let mut palette = vec![0u32; 256];
for i in 0..256 { for i in 0..256 {
@@ -18,11 +21,19 @@ fn main() {
let mut fps_start = Instant::now(); let mut fps_start = Instant::now();
let mut fps_counter = 0; let mut fps_counter = 0;
uw8_window::run(true, move |gpu_framebuffer, _gamepads, _reset| { let mut window_config = WindowConfig::default();
for _ in 0..1 { window_config.parse_arguments(&mut args);
draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32());
let mut window = uw8_window::Window::new(window_config).unwrap();
while window.is_open() {
let input = window.begin_frame();
if input.reset {
start_time = Instant::now();
} }
gpu_framebuffer.update(&framebuffer, bytemuck::cast_slice(&palette)); draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32());
window.end_frame(&framebuffer, bytemuck::cast_slice(&palette), Instant::now());
fps_counter += 1; fps_counter += 1;
let elapsed = fps_start.elapsed().as_secs_f32(); let elapsed = fps_start.elapsed().as_secs_f32();
if elapsed >= 1.0 { if elapsed >= 1.0 {
@@ -30,8 +41,7 @@ fn main() {
fps_start = Instant::now(); fps_start = Instant::now();
fps_counter = 0; fps_counter = 0;
} }
Instant::now() }
});
} }
fn draw_frame(framebuffer: &mut [u8], time: f32) { fn draw_frame(framebuffer: &mut [u8], time: f32) {

View File

@@ -63,7 +63,7 @@ class APU extends AudioWorkletProcessor {
this.memory = memory; 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); this.port.postMessage(2);
} }

View File

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

View File

@@ -263,6 +263,10 @@ export default function MicroW8(screen, config = {}) {
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal }); window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
updateVisibility(document.hasFocus()); updateVisibility(document.hasFocus());
if (instance.exports.start) {
instance.exports.start();
}
function mainloop() { function mainloop() {
if (!keepRunning) { if (!keepRunning) {
return; return;
@@ -270,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;
@@ -317,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);