mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
Compare commits
149 Commits
browser-ru
...
f4fa9e9c62
| Author | SHA1 | Date | |
|---|---|---|---|
| f4fa9e9c62 | |||
| 4c1ce8075e | |||
| 94764d631e | |||
| 8913fdaf6b | |||
| 195376cd28 | |||
| bf14d4bd3f | |||
| 0c68328700 | |||
| fb3fd5e8bb | |||
| b85f96b0bb | |||
| b69055b58d | |||
| 0878aa3f02 | |||
| 5cd1ecb98a | |||
| 842cf9f139 | |||
| d5ed809dc7 | |||
| 285d9aee2c | |||
| 4e18d38538 | |||
| d4bad3ba8b | |||
| 6ddda24156 | |||
| 7003db3a05 | |||
| 8258bc1854 | |||
| a651107104 | |||
| 22c35e37f4 | |||
| 805c939097 | |||
| 440e150896 | |||
| 77b2e27346 | |||
| 09e4fcbf14 | |||
| c8a9f533ef | |||
| 4a1d607bcb | |||
| bbb75838cd | |||
| dbeb242fb2 | |||
| 4fe4bce0ad | |||
| fe86153562 | |||
| c9dadaca2e | |||
| 5dc3e281ce | |||
| ce3afb821f | |||
| 2652a351ad | |||
| 9109722409 | |||
| f861c262a1 | |||
| bbfb5eba49 | |||
| 9e19ff1761 | |||
| 5d85aeac09 | |||
| 30eb953d5d | |||
| abdf780533 | |||
| fc05a54e2c | |||
| b33099c828 | |||
| d478d3ad49 | |||
| 502852e59a | |||
| 5efa8b3465 | |||
| daf2a02cd8 | |||
| 8d5374a867 | |||
| 142b6a4c15 | |||
| 877fceb089 | |||
| f0ba0f2b99 | |||
| 760664eb77 | |||
| 465e66ff4b | |||
| e4579d81bc | |||
| 1f5042059c | |||
| 499bb02f2c | |||
| 57a92ba79a | |||
| 7ec1e68a00 | |||
| 539d19e0d7 | |||
| e9a5f702b4 | |||
| ba0b037ec2 | |||
| 0130d1c906 | |||
| 379ece5cbf | |||
| c9c5cb76bd | |||
| a6d6615231 | |||
| fbc86fa78d | |||
| eb724e8785 | |||
| f559c5b7d4 | |||
| 9dabf75732 | |||
| b0adf7748d | |||
| 7aa70ef39d | |||
| 2ce91ef49c | |||
| 7caad08b7c | |||
| 1f6de62e5d | |||
| caeaa82787 | |||
| e0450c9039 | |||
| 95d0d92a6f | |||
| 7a6dd0ab6d | |||
| e7a00dd9c6 | |||
| a02243d98c | |||
| 599873890a | |||
| 8e9bb002bc | |||
| b2b990333e | |||
| d1556f7be8 | |||
| 9f548cd6f0 | |||
| 7cea4eebd3 | |||
| 3f67e92c5c | |||
| a2714f25e4 | |||
| 7e203d93e6 | |||
| e44c87d1f6 | |||
| 614b7cf358 | |||
| c42a484adb | |||
| 3a5f2bf865 | |||
| 2dee1b30a4 | |||
| 42f7887ab2 | |||
| 4c82f4ad02 | |||
| 4dd8c3b029 | |||
| 2bf8938183 | |||
| 491bf88ade | |||
| e05701300c | |||
| df0c169d54 | |||
| 61941bceeb | |||
| 8fa64519e4 | |||
| 7a52ce4e4c | |||
| 6f20d303c8 | |||
| a5edeb21d8 | |||
| 2839fe5be4 | |||
| 893158e136 | |||
| 7c5f43f152 | |||
| f32b0762b0 | |||
| 9ebb6b6d34 | |||
| 8a10b99eeb | |||
| 6c064a1dd8 | |||
| 37f12f5a2c | |||
| 8ad2885a55 | |||
| 1917057b81 | |||
| 82c1ddb867 | |||
| 8713aa8930 | |||
| 0f82e6e711 | |||
| 0ade24ebf6 | |||
| 29186c806f | |||
| b626d2609a | |||
| 39ead8220f | |||
| ce18a8a162 | |||
| a15e796489 | |||
| f178076b86 | |||
| 81adcf0198 | |||
| 780caf965a | |||
| 2033f9a172 | |||
| 0d514c7dd3 | |||
| a8eb3bda27 | |||
| 8b765a5742 | |||
| 7197c11586 | |||
| 99a423619e | |||
| 9063e872d3 | |||
| 85240599e8 | |||
| 35ec5fdb59 | |||
| a6a82ff5a1 | |||
| 973814a629 | |||
| 00d21b4745 | |||
| d11b46576a | |||
| add49a1f8b | |||
| 8815a8e02e | |||
| eb7c33d412 | |||
| 47ad3b4f30 | |||
| 0f668fb6e9 | |||
| f876f59e80 |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y libxkbcommon-dev
|
||||
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache build dirs
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Build
|
||||
run: cargo build --release --verbose
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: uw8-${{ matrix.build }}
|
||||
path: target/release/${{ matrix.exe }}
|
||||
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -8,12 +8,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: build_and_deploy
|
||||
uses: shalzz/zola-deploy-action@v0.14.1
|
||||
uses: shalzz/zola-deploy-action@70a101a14bbdeed13e7a42a9ed06b35c9e9e826e
|
||||
env:
|
||||
# Target branch
|
||||
PAGES_BRANCH: gh-pages
|
||||
BUILD_DIR: site
|
||||
# Provide personal access token
|
||||
TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4099
Cargo.lock
generated
4099
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -1,26 +1,31 @@
|
||||
[package]
|
||||
name = "uw8"
|
||||
version = "0.1.1"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["native", "browser"]
|
||||
native = ["wasmtime"]
|
||||
native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
|
||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||
|
||||
[dependencies]
|
||||
wasmtime = { version = "0.30", optional = true }
|
||||
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime.git", rev = "0f48f939b9870036562ca02ff21253547a9f1a5c", optional = true }
|
||||
anyhow = "1"
|
||||
minifb = { version = "0.20", default-features = false, features = ["x11"] }
|
||||
notify = "4"
|
||||
pico-args = "0.4"
|
||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b" }
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4"
|
||||
uw8-window = { path = "uw8-window", optional = true }
|
||||
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||
pico-args = "0.5"
|
||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
|
||||
wat = "1"
|
||||
uw8-tool = { path = "uw8-tool" }
|
||||
same-file = "1"
|
||||
warp = { version = "0.3.2", optional = true }
|
||||
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
||||
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
||||
webbrowser = { version = "0.6.0", optional = true }
|
||||
warp = { version = "0.3.6", optional = true }
|
||||
tokio = { version = "1.37.0", features = ["sync", "rt"], optional = true }
|
||||
tokio-stream = { version = "0.1.15", features = ["sync"], optional = true }
|
||||
webbrowser = { version = "0.8.13", optional = true }
|
||||
ansi_term = "0.12.1"
|
||||
cpal = { version = "0.15.3", optional = true }
|
||||
rubato = { version = "0.12.0", optional = true }
|
||||
|
||||
28
README.md
28
README.md
@@ -15,9 +15,9 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
|
||||
|
||||
## Downloads
|
||||
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-windows.zip)
|
||||
|
||||
The download includes
|
||||
|
||||
@@ -35,6 +35,7 @@ Runs <file> which can be a binary WebAssembly module, an `.uw8` cart, a wat (Web
|
||||
|
||||
Options:
|
||||
|
||||
-b, --browser : Run in browser instead of using native runtime
|
||||
-t, --timeout FRAMES : Sets the timeout in frames (1/60s)
|
||||
-w, --watch : Reloads the given file every time it changes on disk.
|
||||
-p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size.
|
||||
@@ -42,6 +43,27 @@ Options:
|
||||
-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.
|
||||
|
||||
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>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.printString" fn printString(i32);
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
printString(0x20000);
|
||||
|
||||
55
examples/curlywas/cracklebass.cwa
Normal file
55
examples/curlywas/cracklebass.cwa
Normal file
@@ -0,0 +1,55 @@
|
||||
// port of cracklebass by pestis (originally on TIC-80)
|
||||
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
const MUSIC_DATA = 0x20000;
|
||||
|
||||
export fn upd() {
|
||||
let inline t = 32!32 * 6 / 100;
|
||||
let inline p = t / 1024;
|
||||
|
||||
let channel:i32;
|
||||
|
||||
loop channels {
|
||||
let inline e = t * channel?MUSIC_DATA / 8;
|
||||
let lazy pattern = (8 * channel + p)?(MUSIC_DATA + 56);
|
||||
let lazy n = !!pattern * (8 * pattern + e / 16 % 8)?MUSIC_DATA;
|
||||
let inline prev_ctrl = (channel * 6)?80;
|
||||
(channel * 6)?80 = if n {
|
||||
let inline base_note = 12 + 12 * channel?(MUSIC_DATA + 4) + n;
|
||||
let inline pitch_drop = e % 16 * channel?(MUSIC_DATA + 94);
|
||||
let inline key_pattern = p?(MUSIC_DATA + 8*4 + 56);
|
||||
let inline key = select(key_pattern, (8 * key_pattern + t / 128 % 8)?MUSIC_DATA, 1);
|
||||
(channel * 6)?83 = base_note - pitch_drop / 4 + key;
|
||||
prev_ctrl & 0xfc | (e / 8 & 2) | 1
|
||||
} else {
|
||||
prev_ctrl & 0xfe
|
||||
};
|
||||
|
||||
branch_if (channel := channel + 1) < 4: channels;
|
||||
}
|
||||
}
|
||||
|
||||
data 80 {
|
||||
i8(
|
||||
0x44, 0, 0, 0, 0x50, 0x40,
|
||||
0x4, 0x50, 0, 0, 0x80, 0x80,
|
||||
0x40, 0x80, 0, 0, 0x40, 0x40,
|
||||
0, 0, 0, 0, 0x50, 0x50
|
||||
)
|
||||
}
|
||||
|
||||
data MUSIC_DATA {
|
||||
i8(
|
||||
16, 2, 8, 8, 1, 2, 2, 3, 1, 0,
|
||||
1,13,16, 0, 1, 8, 1, 0, 1,13,
|
||||
16, 1, 1, 8, 1, 0, 8,13,13, 0,
|
||||
16,13, 1, 0, 1, 0, 1, 0, 1, 1,
|
||||
1, 0, 0, 0, 1, 0,13, 1, 1, 1,
|
||||
6, 8, 1, 1, 6, 8, 1, 1, 2, 1,
|
||||
2, 1, 2, 0, 0, 0, 0, 3, 3, 3,
|
||||
5, 0, 0, 2, 1, 2, 1, 2, 1, 2,
|
||||
0, 4, 4, 0, 4, 4, 4, 4, 0, 0,
|
||||
0, 0, 6, 6, 0, 0, 0, 8
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
import "env.time" fn time() -> f32;
|
||||
import "env.circle" fn circle(f32, f32, f32, i32);
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.randomSeed" fn seed(i32);
|
||||
import "env.randomf" fn randomf() -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
@@ -15,10 +8,10 @@ export fn upd() {
|
||||
let inline rocket = i #>> 9;
|
||||
let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32);
|
||||
let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10;
|
||||
seed(rocket);
|
||||
randomSeed(rocket);
|
||||
let inline x = randomf() * 645 as f32;
|
||||
let y = randomf() * 133 as f32;
|
||||
let lazy angle = { seed(i); randomf() } * 44 as f32;
|
||||
let lazy angle = { randomSeed(i); randomf() } * 44 as f32;
|
||||
let inline dx = sin(angle);
|
||||
let inline dy = cos(angle);
|
||||
let lazy dist = local_time * (randomf() * 44 as f32);
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.printString" fn printString(i32);
|
||||
import "env.printChar" fn printChar(i32);
|
||||
import "env.setCursorPosition" fn setCursor(i32, i32);
|
||||
import "env.setTextColor" fn setTextColor(i32);
|
||||
import "env.line" fn line(f32, f32, f32, f32, i32);
|
||||
import "env.isButtonTriggered" fn triggered(i32) -> i32;
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
global mut mode: i32 = 0;
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
|
||||
if triggered(4) {
|
||||
if isButtonTriggered(BUTTON_A) {
|
||||
mode = !mode;
|
||||
}
|
||||
|
||||
setTextColor(15);
|
||||
printString(mode * 0x20000);
|
||||
printString(mode * USER_MEM);
|
||||
|
||||
let y: i32;
|
||||
loop y {
|
||||
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
|
||||
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
|
||||
setTextColor(15);
|
||||
setCursor(y * 9 + 16, 24);
|
||||
setCursorPosition(y * 9 + 16, 24);
|
||||
let lazy hexChar = select(y < 10, y + 48, y + 87);
|
||||
printChar(hexChar);
|
||||
setCursor(0, y * 9 + 24+16);
|
||||
setCursorPosition(0, y * 9 + 24+16);
|
||||
printChar(hexChar);
|
||||
let x = 0;
|
||||
loop x {
|
||||
setCursor(x * 9 + 16, y * 9 + 24+16);
|
||||
setCursorPosition(x * 9 + 16, y * 9 + 24+16);
|
||||
setTextColor(select(mode, x + y * 16, -9));
|
||||
if y >= 2 | mode {
|
||||
printChar(select(mode, 0xa4, x + y * 16));
|
||||
@@ -47,6 +39,6 @@ data 0 {
|
||||
"Default font: (press " i8(0xcc) " for palette)" i8(5, 0)
|
||||
}
|
||||
|
||||
data 0x20000 {
|
||||
data USER_MEM {
|
||||
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.time" fn time() -> f32;
|
||||
import "env.line" fn line(f32, f32, f32, f32, i32);
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
|
||||
14
examples/curlywas/scaled_text.cwa
Normal file
14
examples/curlywas/scaled_text.cwa
Normal file
@@ -0,0 +1,14 @@
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
printString(USER_MEM);
|
||||
}
|
||||
|
||||
data USER_MEM {
|
||||
// clear screen, switch to graphics text mode, text scale 4
|
||||
i8(12, 5, 30, 4)
|
||||
// text color, position, print two lines
|
||||
i8(15, 86, 31, 8, 80) "Hello," i8(8, 8, 8, 8, 8, 10) "MicroW8!"
|
||||
// print same two lines with different color and slight offset
|
||||
i8(15, 47, 31, 10, 82) "Hello," i8(8, 8, 8, 8, 8, 10) "MicroW8!" i8(0)
|
||||
}
|
||||
38
examples/curlywas/simple_music.cwa
Normal file
38
examples/curlywas/simple_music.cwa
Normal file
@@ -0,0 +1,38 @@
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
global mut frame = 0;
|
||||
|
||||
export fn upd() {
|
||||
if frame % 16 == 0 {
|
||||
let ch: i32;
|
||||
loop channels {
|
||||
playNote(ch, (ch * 32 + (frame / 16) % 32)?0x20000);
|
||||
branch_if ch := (ch + 1) % 4: channels;
|
||||
}
|
||||
}
|
||||
frame = frame + 1;
|
||||
}
|
||||
|
||||
data 0x20000 {
|
||||
i8(
|
||||
0x4e, 0x0, 0x0, 0x4c, 0x49, 0x0, 0x45, 0x47,
|
||||
0x49, 0x47, 0x45, 0x44, 0x42, 0x0, 0x3d, 0x41,
|
||||
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
|
||||
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
|
||||
|
||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||
|
||||
0x2a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0,
|
||||
0x2c, 0x0, 0x28, 0x0, 0x2a, 0x0, 0x0, 0x0,
|
||||
0x25, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0,
|
||||
0x2c, 0x0, 0x2d, 0x0, 0x2a, 0x0, 0x25, 0x0,
|
||||
|
||||
0x0, 0x0, 0x31, 0x0, 0x34, 0x0, 0x0, 0x36,
|
||||
0x38, 0x39, 0x38, 0x34, 0x36, 0x0, 0x0, 0x0,
|
||||
0x0, 0x3d, 0x3b, 0x39, 0x38, 0x0, 0x0, 0x0,
|
||||
0x0, 0x39, 0x38, 0x39, 0x38, 0x0, 0x36, 0x0
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,4 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
|
||||
import "env.circle" fn circle(f32, f32, f32, i32);
|
||||
import "env.isButtonPressed" fn btn(i32) -> i32;
|
||||
import "env.random" fn random() -> i32;
|
||||
import "env.randomSeed" fn randomSeed(i32);
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.printInt" fn printInt(i32);
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
global mut pz: i32 = 4;
|
||||
global mut px: f32 = 2.0;
|
||||
@@ -16,10 +8,10 @@ global mut f: f32 = 2.0;
|
||||
|
||||
export fn upd() {
|
||||
let y: i32;
|
||||
let inline zero = 0.0;
|
||||
let inline zero = 0_f;
|
||||
|
||||
let lazy control_speed = 0.03125;
|
||||
s = s + 0.1875 - (f + control_speed) * btn(4 <| cls(4)) as f32;
|
||||
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
|
||||
f = f * 0.5625;
|
||||
|
||||
printInt(pz);
|
||||
@@ -33,11 +25,13 @@ export fn upd() {
|
||||
|
||||
let inline c = (z & 1) * -2;
|
||||
let inline yf = y as f32;
|
||||
rect(rx, yf, rw, yf / 6 as f32, c + 1);
|
||||
rect(rx, yf, rw, 1 as f32, c - 4);
|
||||
rectangle(rx, yf, rw, yf / 6 as f32, c + 1);
|
||||
rectangle(rx, yf, rw, 1 as f32, c - 4);
|
||||
|
||||
if y == 180 & py > zero {
|
||||
if x > w | x < zero {
|
||||
0?80 = 0xc3;
|
||||
3?80 = 32;
|
||||
return;
|
||||
}
|
||||
py = zero;
|
||||
@@ -51,7 +45,10 @@ export fn upd() {
|
||||
circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
|
||||
circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26);
|
||||
|
||||
px = px + (btn(3) - btn(2)) as f32 * control_speed;
|
||||
0?86 = py < zero;
|
||||
3?86 = 32 - py as i32;
|
||||
|
||||
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
|
||||
py = py + s;
|
||||
pz = pz + 1;
|
||||
}
|
||||
|
||||
21
examples/curlywas/sprites.cwa
Normal file
21
examples/curlywas/sprites.cwa
Normal file
@@ -0,0 +1,21 @@
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
const SPRITE = 0x20000;
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
let t = time() / 2_f;
|
||||
let i: i32;
|
||||
loop spriteLoop {
|
||||
let inline x = sin(t * -1.3 + i as f32 * (3.141 / 30_f)) * 180_f + 160_f;
|
||||
let inline y = sin(t * 1.7 + i as f32 * (3.141 / 40_f)) * 140_f + 120_f;
|
||||
blitSprite(SPRITE, 16, x as i32, y as i32, (i & 3) * 0x200 + 0x100);
|
||||
branch_if (i +:= 1) < 100: spriteLoop;
|
||||
}
|
||||
}
|
||||
|
||||
start fn start() {
|
||||
printChar('OO');
|
||||
circle(8_f, 8_f, 6_f, 75);
|
||||
grabSprite(SPRITE, 16, 0, 0, 0);
|
||||
}
|
||||
38
examples/curlywas/steadyon.cwa
Normal file
38
examples/curlywas/steadyon.cwa
Normal file
@@ -0,0 +1,38 @@
|
||||
// Steady On Tim, It's Only A Budget Game
|
||||
// by Gasman / Hooy-Program
|
||||
// ported to MicroW8 by exoticorn/icebird
|
||||
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
fn melody(t: i32, T: i32) -> i32 {
|
||||
let inline riff_pos = abs(((T&31) - 16) as f32) as i32;
|
||||
let lazy shift = ((1-((T>>5)&3))%2-1) as f32 / 6 as f32;
|
||||
|
||||
let inline note_count = 5 - (T >= 512);
|
||||
let inline octave = (riff_pos/5) as f32;
|
||||
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
|
||||
let inline melody_freq = pow(2 as f32, shift + octave - (riff_note as f32 / 12 as f32));
|
||||
let inline melody = (t as f32 * melody_freq) as i32 & 128;
|
||||
|
||||
let inline arp_note = ((0x85>>((t>>12)%3*4)) & 15) - 1;
|
||||
let inline arp_freq = pow(2 as f32, shift + (arp_note as f32 / 12 as f32));
|
||||
let inline arp_vol = (T >= 256) * (12-T%12);
|
||||
let inline arpeggio = ((t as f32 * arp_freq) as i32 & 128) * arp_vol / 12;
|
||||
|
||||
melody + arpeggio
|
||||
}
|
||||
|
||||
export fn snd(t: i32) -> f32 {
|
||||
let lazy T = t/10000;
|
||||
|
||||
let inline mel_arp = melody(t, T)/3 + melody(t, T-3)/5;
|
||||
|
||||
let inline bass_vol = (T >= 128) & (197 >> (T % 8));
|
||||
let inline bass_freq = pow(2 as f32, (((T & 4) * ((T & 7) - 1)) as f32 / 24 as f32 - 5 as f32));
|
||||
let inline bass = ((t as f32 * bass_freq) as i32 & 63) * bass_vol;
|
||||
|
||||
let inline snare_ish = (random() & 31) * (8 - (T + 4) % 8) / 8;
|
||||
|
||||
let inline sample = mel_arp + bass + snare_ish;
|
||||
sample as f32 / 255 as f32
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.time" fn time() -> f32;
|
||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
let x: i32;
|
||||
|
||||
64
examples/curlywas/tim_ges.cwa
Normal file
64
examples/curlywas/tim_ges.cwa
Normal file
@@ -0,0 +1,64 @@
|
||||
// Steady On Tim, It's Only A Budget Game
|
||||
// original bytebeat by Gasman / Hooy-Program
|
||||
// ported to MicroW8/GES by exoticorn/icebird
|
||||
|
||||
import "env.memory" memory(4);
|
||||
|
||||
fn melody(ch: i32, t: i32, T: i32) {
|
||||
let lazy riff_pos = abs(((T&31) - 16) as f32) as i32;
|
||||
let lazy shift = ((1-((T>>5)&3))%2-1) * 2;
|
||||
|
||||
let inline note_count = 5 - (T >= 512);
|
||||
let inline octave = (riff_pos/5) * 12;
|
||||
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
|
||||
let inline melody_note = shift + octave - riff_note;
|
||||
|
||||
ch?1 = 230 - riff_pos * 14;
|
||||
ch?3 = melody_note + 64;
|
||||
|
||||
let inline arp_note = shift + ((0x85>>((t/2)%3*4)) & 15) - 1;
|
||||
80?3 = arp_note + 64;
|
||||
}
|
||||
|
||||
export fn upd() {
|
||||
let lazy t = 32!32 / (1000/60);
|
||||
let lazy T = t / 7;
|
||||
melody(98, t, T - 3);
|
||||
melody(92, t, T);
|
||||
|
||||
80?0 = ((T >= 256) & (T/12+(T-3)/12)) * 2 | 0x48; // arp trigger
|
||||
|
||||
if T >= 128 {
|
||||
let inline bass_step = T % 8;
|
||||
86?3 = if bass_step / 2 == 2 {
|
||||
86?0 = 0xd6;
|
||||
81
|
||||
} else {
|
||||
86?0 = ((197 >> bass_step) & 1) | 0x48;
|
||||
((T & 4) * ((T & 7) - 1)) / 2 + 28
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
data 80 {
|
||||
i8(
|
||||
0, 0x90, 0, 0, 0, 0x90,
|
||||
0, 0x4c, 0, 0, 0, 0x4c,
|
||||
0x19, 0, 0, 0, 0, 0x4c,
|
||||
0x19, 0, 0, 0, 0, 0x4c,
|
||||
0xfa, 0x84,
|
||||
0xc1, 0xc1, 0, 107, 0, 0x4c
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
include "../../platform/src/ges.cwa"
|
||||
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
|
||||
export fn snd(t: i32) -> f32 {
|
||||
gesSnd(t)
|
||||
}
|
||||
*/
|
||||
@@ -1,7 +1,4 @@
|
||||
import "env.memory" memory(2);
|
||||
|
||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
||||
import "env.time" fn time() -> f32;
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
let i: i32;
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.atan2" fn atan2(f32, f32) -> f32;
|
||||
import "env.time" fn time() -> f32;
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
let i: i32;
|
||||
loop pixels {
|
||||
let inline t = time() * 63 as f32;
|
||||
let lazy x = (i % 320 - 160) as f32;
|
||||
let lazy y = (i / 320 - 120) as f32;
|
||||
let inline d = 40000 as f32 / sqrt(x * x + y * y);
|
||||
let inline u = atan2(x, y) * (512.0 / 3.141);
|
||||
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
|
||||
i?120 = c;
|
||||
let inline t = 16!56;
|
||||
let inline x = (i % 320 - 160) as f32;
|
||||
let inline y = (i #/ 320 - 120) as f32;
|
||||
let inline d = 0xa000 as f32 / sqrt(x * x + y * y);
|
||||
let inline a = atan2(x, y) * 163_f; // (512 / pi)
|
||||
let inline u = i32.trunc_sat_f32_s(a) + t;
|
||||
let inline v = i32.trunc_sat_f32_s(d) + t * 2;
|
||||
let inline c = ((v ^ u) #/ 16) % 16;
|
||||
i?FRAMEBUFFER = c;
|
||||
|
||||
branch_if (i := i + 1) < 320*240: pixels;
|
||||
}
|
||||
|
||||
54
examples/include/microw8-api.cwa
Normal file
54
examples/include/microw8-api.cwa
Normal file
@@ -0,0 +1,54 @@
|
||||
// MicroW8 APIs, to be `include`d in CurlyWas sources
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
import "env.tan" fn tan(f32) -> f32;
|
||||
import "env.asin" fn asin(f32) -> f32;
|
||||
import "env.acos" fn acos(f32) -> f32;
|
||||
import "env.atan" fn atan(f32) -> f32;
|
||||
import "env.atan2" fn atan2(f32, f32) -> f32;
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.log" fn log(f32) -> f32;
|
||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
||||
import "env.random" fn random() -> i32;
|
||||
import "env.randomf" fn randomf() -> f32;
|
||||
import "env.randomSeed" fn randomSeed(i32);
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
||||
import "env.getPixel" fn getPixel(i32, i32) -> i32;
|
||||
import "env.hline" fn hline(i32, i32, i32, i32);
|
||||
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||
import "env.circle" fn circle(f32, f32, f32, i32);
|
||||
import "env.line" fn line(f32, f32, f32, f32, i32);
|
||||
import "env.time" fn time() -> f32;
|
||||
import "env.isButtonPressed" fn isButtonPressed(i32) -> i32;
|
||||
import "env.isButtonTriggered" fn isButtonTriggered(i32) -> i32;
|
||||
import "env.printChar" fn printChar(i32);
|
||||
import "env.printString" fn printString(i32);
|
||||
import "env.printInt" fn printInt(i32);
|
||||
import "env.setTextColor" fn setTextColor(i32);
|
||||
import "env.setBackgroundColor" fn setBackgroundColor(i32);
|
||||
import "env.setCursorPosition" fn setCursorPosition(i32, i32);
|
||||
import "env.rectangleOutline" fn rectangleOutline(f32, f32, f32, f32, i32);
|
||||
import "env.circleOutline" fn circleOutline(f32, f32, f32, i32);
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.playNote" fn playNote(i32, i32);
|
||||
import "env.sndGes" fn sndGes(i32) -> f32;
|
||||
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 GAMEPAD = 0x44;
|
||||
const FRAMEBUFFER = 0x78;
|
||||
const PALETTE = 0x13000;
|
||||
const FONT = 0x13400;
|
||||
const USER_MEM = 0x14000;
|
||||
const BUTTON_UP = 0x0;
|
||||
const BUTTON_DOWN = 0x1;
|
||||
const BUTTON_LEFT = 0x2;
|
||||
const BUTTON_RIGHT = 0x3;
|
||||
const BUTTON_A = 0x4;
|
||||
const BUTTON_B = 0x5;
|
||||
const BUTTON_X = 0x6;
|
||||
const BUTTON_Y = 0x7;
|
||||
56
examples/include/microw8-api.wat
Normal file
56
examples/include/microw8-api.wat
Normal file
@@ -0,0 +1,56 @@
|
||||
;; MicroW8 APIs, in WAT (Wasm Text) format
|
||||
(import "env" "memory" (memory 4))
|
||||
|
||||
(import "env" "sin" (func $sin (param f32) (result f32)))
|
||||
(import "env" "cos" (func $cos (param f32) (result f32)))
|
||||
(import "env" "tan" (func $tan (param f32) (result f32)))
|
||||
(import "env" "asin" (func $asin (param f32) (result f32)))
|
||||
(import "env" "acos" (func $acos (param f32) (result f32)))
|
||||
(import "env" "atan" (func $atan (param f32) (result f32)))
|
||||
(import "env" "atan2" (func $atan2 (param f32) (param f32) (result f32)))
|
||||
(import "env" "pow" (func $pow (param f32) (param f32) (result f32)))
|
||||
(import "env" "log" (func $log (param f32) (result f32)))
|
||||
(import "env" "fmod" (func $fmod (param f32) (param f32) (result f32)))
|
||||
(import "env" "random" (func $random (result i32)))
|
||||
(import "env" "randomf" (func $randomf (result f32)))
|
||||
(import "env" "randomSeed" (func $randomSeed (param i32)))
|
||||
(import "env" "cls" (func $cls (param i32)))
|
||||
(import "env" "setPixel" (func $setPixel (param i32) (param i32) (param i32)))
|
||||
(import "env" "getPixel" (func $getPixel (param i32) (param i32) (result i32)))
|
||||
(import "env" "hline" (func $hline (param i32) (param i32) (param i32) (param i32)))
|
||||
(import "env" "rectangle" (func $rectangle (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "circle" (func $circle (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "line" (func $line (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "time" (func $time (result f32)))
|
||||
(import "env" "isButtonPressed" (func $isButtonPressed (param i32) (result i32)))
|
||||
(import "env" "isButtonTriggered" (func $isButtonTriggered (param i32) (result i32)))
|
||||
(import "env" "printChar" (func $printChar (param i32)))
|
||||
(import "env" "printString" (func $printString (param i32)))
|
||||
(import "env" "printInt" (func $printInt (param i32)))
|
||||
(import "env" "setTextColor" (func $setTextColor (param i32)))
|
||||
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
|
||||
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
|
||||
(import "env" "rectangleOutline" (func $rectangleOutline (param f32) (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" "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
|
||||
;; like gpp (https://logological.org/gpp).
|
||||
#define TIME_MS 0x40;
|
||||
#define GAMEPAD 0x44;
|
||||
#define FRAMEBUFFER 0x78;
|
||||
#define PALETTE 0x13000;
|
||||
#define FONT 0x13400;
|
||||
#define USER_MEM 0x14000;
|
||||
#define BUTTON_UP 0x0;
|
||||
#define BUTTON_DOWN 0x1;
|
||||
#define BUTTON_LEFT 0x2;
|
||||
#define BUTTON_RIGHT 0x3;
|
||||
#define BUTTON_A 0x4;
|
||||
#define BUTTON_B 0x5;
|
||||
#define BUTTON_X 0x6;
|
||||
#define BUTTON_Y 0x7;
|
||||
74
examples/wat/tunnel_opt.wat
Normal file
74
examples/wat/tunnel_opt.wat
Normal file
@@ -0,0 +1,74 @@
|
||||
(module
|
||||
(import "env" "atan2" (func $atan2 (param f32 f32) (result f32)))
|
||||
(import "env" "time" (func $time (result f32)))
|
||||
(import "env" "memory" (memory 4))
|
||||
(func (export "upd")
|
||||
(local $y i32)
|
||||
(local $i i32)
|
||||
(local $x i32)
|
||||
|
||||
(loop $pixels
|
||||
i32.const 1
|
||||
local.get $i
|
||||
|
||||
local.get $i
|
||||
|
||||
i32.const 36928
|
||||
f32.convert_i32_s
|
||||
local.get $i
|
||||
i32.const 320
|
||||
i32.rem_s
|
||||
i32.const 160
|
||||
i32.sub
|
||||
local.tee $x
|
||||
local.get $x
|
||||
i32.mul
|
||||
local.get $i
|
||||
i32.const 320
|
||||
i32.div_s
|
||||
i32.const 120
|
||||
i32.sub
|
||||
local.tee $y
|
||||
local.get $y
|
||||
i32.mul
|
||||
i32.add
|
||||
f32.convert_i32_s
|
||||
f32.sqrt
|
||||
f32.div
|
||||
call $time
|
||||
i32.const 163
|
||||
f32.convert_i32_s
|
||||
f32.mul
|
||||
f32.add
|
||||
i32.trunc_sat_f32_s
|
||||
|
||||
local.get $x
|
||||
f32.convert_i32_s
|
||||
local.get $y
|
||||
f32.convert_i32_s
|
||||
call $atan2
|
||||
i32.const 163
|
||||
f32.convert_i32_s
|
||||
f32.mul
|
||||
call $time
|
||||
i32.const 64
|
||||
f32.convert_i32_s
|
||||
f32.mul
|
||||
f32.add
|
||||
i32.trunc_f32_s
|
||||
|
||||
i32.xor
|
||||
i32.const 4
|
||||
i32.shr_s
|
||||
i32.const 15
|
||||
i32.and
|
||||
i32.store8 offset=120
|
||||
|
||||
i32.add
|
||||
local.tee $i
|
||||
i32.const 76800
|
||||
i32.rem_s
|
||||
br_if $pixels
|
||||
)
|
||||
)
|
||||
)
|
||||
425
platform/Cargo.lock
generated
425
platform/Cargo.lock
generated
@@ -19,47 +19,54 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7080ae01b2f0c312065d4914cd0f0de045eb8832e9415b355106a6cff3073cb4"
|
||||
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
|
||||
dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.3"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
|
||||
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||
|
||||
[[package]]
|
||||
name = "cdivsufsort"
|
||||
@@ -79,63 +86,56 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.5.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f"
|
||||
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
|
||||
dependencies = [
|
||||
"ahash 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.13"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.13"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"lazy_static",
|
||||
"proc-macro-hack",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
@@ -146,42 +146,52 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
[[package]]
|
||||
name = "curlywas"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/exoticorn/curlywas.git?rev=196719b#196719b35ef377cb7e001554b27ac5de013dcf2b"
|
||||
source = "git+https://github.com/exoticorn/curlywas.git?rev=0e7ea50#0e7ea508cd0e76836283ae68a44c9097df83c8ac"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ariadne",
|
||||
"chumsky",
|
||||
"pico-args",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
"pico-args 0.4.2",
|
||||
"wasm-encoder 0.10.0",
|
||||
"wasmparser 0.83.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible_collections"
|
||||
version = "0.4.4"
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52db5973b6a19247baf19b30f41c23a1bfffc2e9ce0a5db2f60e3cd5dc8895f7"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible_collections"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -189,19 +199,71 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
name = "gimli"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"fallible-iterator",
|
||||
"indexmap 1.9.3",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
@@ -210,17 +272,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
name = "lexopt"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "lodepng"
|
||||
version = "3.4.7"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24844d5c0b922ddd52fb5bf0964a4c7f8e799a946ec01bb463771eb04fc1a323"
|
||||
checksum = "a42d298694b14401847de29abd44adf278b42e989e516deac7b72018400002d8"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"fallible_collections",
|
||||
"flate2",
|
||||
"libc",
|
||||
@@ -228,39 +297,43 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.9.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "pbr"
|
||||
version = "1.0.4"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2"
|
||||
checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"libc",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -270,6 +343,12 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "platform"
|
||||
version = "0.1.0"
|
||||
@@ -281,16 +360,28 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.31"
|
||||
version = "0.8.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a374af9a0e5fdcdd98c1c7b64f05004f9ea2555b6c75f211daa81268a3c50f1"
|
||||
checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
@@ -305,13 +396,57 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
name = "semver"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -323,15 +458,27 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "upkr"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cdivsufsort",
|
||||
"pbr",
|
||||
"pico-args",
|
||||
"lexopt",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -340,38 +487,102 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"pbr",
|
||||
"pico-args",
|
||||
"pico-args 0.5.0",
|
||||
"upkr",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
"walrus",
|
||||
"wasm-encoder 0.201.0",
|
||||
"wasmparser 0.201.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walrus"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c03529cd0c4400a2449f640d2f27cd1b48c3065226d15e26d98e4429ab0adb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gimli",
|
||||
"id-arena",
|
||||
"leb128",
|
||||
"log",
|
||||
"walrus-macro",
|
||||
"wasm-encoder 0.29.0",
|
||||
"wasmparser 0.80.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walrus-macro"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
||||
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.81.0"
|
||||
version = "0.80.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
||||
checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.83.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap 2.2.6",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
@@ -397,6 +608,26 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="196719b" }
|
||||
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="0e7ea50" }
|
||||
uw8-tool = { path="../uw8-tool" }
|
||||
anyhow = "1"
|
||||
lodepng = "3.4"
|
||||
lodepng = "3.7.2"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
221
platform/src/ges.cwa
Normal file
221
platform/src/ges.cwa
Normal file
@@ -0,0 +1,221 @@
|
||||
const GesChannelState.Trigger = 0;
|
||||
const GesChannelState.EnvState = 1;
|
||||
const GesChannelState.EnvVol = 2;
|
||||
const GesChannelState.Phase = 4;
|
||||
const GesChannelState.Size = 8;
|
||||
|
||||
const GesState.Filter = GesChannelState.Size * 4;
|
||||
const GesState.Size = GesState.Filter + 8*4;
|
||||
|
||||
const GesStateOffset = 32;
|
||||
const GesBufferOffset = 32 + GesState.Size;
|
||||
|
||||
export fn sndGes(t: i32) -> f32 {
|
||||
let baseAddr = 0!0x12c78;
|
||||
if !(t & 127) {
|
||||
let i: i32;
|
||||
loop clearLoop {
|
||||
(baseAddr + i)!GesBufferOffset = 0;
|
||||
branch_if (i := i + 4) < 128*4: clearLoop;
|
||||
}
|
||||
|
||||
let ch: i32;
|
||||
loop channelLoop {
|
||||
let lazy channelState = baseAddr + GesStateOffset + ch * GesChannelState.Size;
|
||||
let lazy channelReg = baseAddr + ch * 6;
|
||||
let envState = channelState?GesChannelState.EnvState;
|
||||
let envVol = i32.load16_u(channelState, GesChannelState.EnvVol);
|
||||
|
||||
let lazy oldTrigger = channelState?GesChannelState.Trigger;
|
||||
let lazy ctrl = channelReg?0;
|
||||
if (oldTrigger ^ ctrl) & (ctrl | 2) & 3 {
|
||||
envState = 1;
|
||||
envVol = 0;
|
||||
}
|
||||
channelState?GesChannelState.Trigger = ctrl;
|
||||
|
||||
if envState {
|
||||
let lazy attack = channelReg?4 & 15;
|
||||
envVol = envVol + 12 * pow(1.675, (15 - attack) as f32) as i32;
|
||||
if envVol >= 65535 {
|
||||
envVol = 65535;
|
||||
envState = 0;
|
||||
}
|
||||
} else {
|
||||
let inline decay = (channelReg - (ctrl & 1))?5 >> 4;
|
||||
let inline dec = 8 * pow(1.5625, (15 - decay) as f32) as i32;
|
||||
envVol = envVol - ((dec * (envVol + 8192)) >> 16);
|
||||
let inline sustain = (channelReg?5 & 15) << 12;
|
||||
let lazy targetVol = (ctrl & 1) * sustain;
|
||||
if envVol < targetVol {
|
||||
envVol = targetVol;
|
||||
}
|
||||
}
|
||||
channelState?GesChannelState.EnvState = envState;
|
||||
|
||||
i32.store16(envVol, channelState, GesChannelState.EnvVol);
|
||||
|
||||
let inline note = i32.load16_u(channelReg, 2);
|
||||
let lazy freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||
|
||||
let phase = channelState!GesChannelState.Phase;
|
||||
|
||||
let inline pulseWidth = channelReg?1;
|
||||
let invPhaseInc = 1 as f32 / phaseInc as f32;
|
||||
|
||||
i = 0;
|
||||
let wave = ctrl >> 6;
|
||||
if wave < 2 {
|
||||
if wave {
|
||||
let pulsePhase1 = pulseWidth << 23;
|
||||
let pulsePhase2 = (511 - pulseWidth) << 23;
|
||||
loop sawLoop {
|
||||
let p = (phase ^ 32768) << 16;
|
||||
let saw = (p >> 16) - polyBlep(phase, invPhaseInc, -32767);
|
||||
let saw2 = select(p #>= pulsePhase1 & p #< pulsePhase2, -saw, saw);
|
||||
let saw2 = saw2 -
|
||||
polyBlep((p - pulsePhase1) >> 16, invPhaseInc, -saw) -
|
||||
polyBlep((p - pulsePhase2) >> 16, invPhaseInc, saw);
|
||||
(baseAddr + i)!(GesBufferOffset + 128*4) = saw2;
|
||||
phase = phase + phaseInc;
|
||||
branch_if (i := i + 4) < 64*4: sawLoop;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let pulsePhase = 32768 + pulseWidth * 128;
|
||||
loop rectLoop {
|
||||
(baseAddr + i)!(GesBufferOffset + 128*4) = select((phase & 65535) < pulsePhase, -32768, 32767) -
|
||||
polyBlep(phase, invPhaseInc, -32767) -
|
||||
polyBlep(phase - pulsePhase, invPhaseInc, 32767);
|
||||
phase = phase + phaseInc;
|
||||
branch_if (i := i + 4) < 64*4: rectLoop;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if wave == 2 {
|
||||
let scale = pulseWidth + 256;
|
||||
loop triLoop {
|
||||
let s = phase << 16;
|
||||
s = (s ^ (s >> 31));
|
||||
s = (s >> 8) * scale;
|
||||
s = (s ^ (s >> 31));
|
||||
(baseAddr + i)!(GesBufferOffset + 128*4) = (s >> 15) - 32768;
|
||||
phase = phase + phaseInc;
|
||||
branch_if (i := i + 4) < 64*4: triLoop;
|
||||
}
|
||||
} else {
|
||||
loop noiseLoop {
|
||||
let s = phase >> 12;
|
||||
let inline pulse = ((phase >> 8) & 255) >= pulseWidth;
|
||||
s = s * 0x6746ba73;
|
||||
s = s ^ (s >> 15) * pulse;
|
||||
(baseAddr + i)!(GesBufferOffset + 128*4) = (s * 0x835776c7) >> 16;
|
||||
phase = phase + phaseInc;
|
||||
branch_if (i := i + 4) < 64*4: noiseLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channelState!GesChannelState.Phase = phase;
|
||||
|
||||
if ctrl & 32 {
|
||||
let lazy modSrc = (ch - 1) & 3;
|
||||
let inline channelState = baseAddr + GesStateOffset + modSrc * GesChannelState.Size;
|
||||
let inline channelReg = baseAddr + modSrc * 6;
|
||||
|
||||
let inline note = i32.load16_u(channelReg, 2);
|
||||
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||
|
||||
let phase = channelState!GesChannelState.Phase;
|
||||
if modSrc < ch {
|
||||
phase = phase - (phaseInc << 6);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
loop ringLoop {
|
||||
let s = phase << 16;
|
||||
s = (s ^ (s >> 31));
|
||||
(baseAddr + i)!(GesBufferOffset + 128*4) = ((baseAddr + i)!(GesBufferOffset + 128*4) * ((s >> 15) - 32768)) >> 15;
|
||||
phase = phase + phaseInc;
|
||||
branch_if (i := i + 4) < 64*4: ringLoop;
|
||||
}
|
||||
}
|
||||
|
||||
let channelVol = ((baseAddr + (ch >> 1))?24 >> ((ch & 1) * 4)) & 15;
|
||||
envVol = envVol * channelVol / 15;
|
||||
|
||||
let leftVol = (select(ctrl & 16, 0x3d5b, 0x6a79) >> (ch * 4)) & 15;
|
||||
let rightVol = 16 - leftVol;
|
||||
|
||||
let lazy filter = (ctrl >> 2) & 3;
|
||||
|
||||
i = 0;
|
||||
if filter < 2 {
|
||||
if filter {
|
||||
let f = (4096 as f32 - min(4096 as f32, 4096 as f32 * exp(freq * (-8.0 * 3.141 / 44100.0)))) as i32;
|
||||
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
|
||||
loop filterLoop {
|
||||
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||
low = low + (((in - low) * f) >> 12);
|
||||
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((low * leftVol) >> 4);
|
||||
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((low * rightVol) >> 4);
|
||||
branch_if (i := i + 4) < 64*4: filterLoop;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
|
||||
}
|
||||
} else {
|
||||
loop mixLoop {
|
||||
let sample = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
|
||||
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
|
||||
branch_if (i := i + 4) < 64*4: mixLoop;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = sample;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filter = filter - 2;
|
||||
let ctrl = (baseAddr + filter)?26;
|
||||
let note = i32.load16_u(baseAddr + filter * 2, 28);
|
||||
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||
let F = (8192 as f32 * sin(min(0.25, freq / 44100 as f32) * 3.1415)) as i32;
|
||||
let Q = 8192 - (ctrl >> 4) * (7000/15);
|
||||
let Qlimit = (8192*4096/F - F/2) * 3 / 4;
|
||||
if Q > Qlimit {
|
||||
Q = Qlimit;
|
||||
}
|
||||
let low_out = ctrl & 1;
|
||||
let high_out = (ctrl >> 1) & 1;
|
||||
let band_out = (ctrl >> 2) & 1;
|
||||
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
|
||||
let band = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4);
|
||||
loop filterLoop {
|
||||
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||
|
||||
let high = in - low - ((band * Q) >> 12);
|
||||
band = band + ((F * high) >> 12);
|
||||
low = low + ((F * band) >> 12);
|
||||
|
||||
let sample = low * low_out + high * high_out + band * band_out;
|
||||
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
|
||||
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
|
||||
branch_if (i := i + 4) < 64*4: filterLoop;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
|
||||
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = band;
|
||||
}
|
||||
}
|
||||
|
||||
branch_if (ch := ch + 1) < 4: channelLoop;
|
||||
}
|
||||
}
|
||||
((baseAddr + (t & 127) * 4)!GesBufferOffset) as f32 / 32768 as f32
|
||||
}
|
||||
|
||||
fn polyBlep(transientPhase: i32, invPhaseInc: f32, magnitude: i32) -> i32 {
|
||||
let lazy t = ((transientPhase << 16) >> 16) as f32 * invPhaseInc;
|
||||
let lazy x = max(0 as f32, 1 as f32 - abs(t));
|
||||
(f32.copysign(x * x, t) * magnitude as f32) as i32
|
||||
}
|
||||
6
platform/src/ges_only.cwa
Normal file
6
platform/src/ges_only.cwa
Normal file
@@ -0,0 +1,6 @@
|
||||
import "env.memory" memory(1);
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
|
||||
include "ges.cwa"
|
||||
@@ -11,13 +11,13 @@ fn main() -> Result<()> {
|
||||
convert_font()?;
|
||||
|
||||
println!("Compiling loader module");
|
||||
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default())?;
|
||||
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default()).0?;
|
||||
File::create("bin/loader.wasm")?.write_all(&loader)?;
|
||||
|
||||
println!("Loader (including base module): {} bytes", loader.len());
|
||||
|
||||
println!("Compiling platform module");
|
||||
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default())?;
|
||||
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default()).0?;
|
||||
println!("Compressing platform module");
|
||||
let platform = uw8_tool::pack(
|
||||
&platform,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.logChar" fn logChar(i32);
|
||||
|
||||
export fn time() -> f32 {
|
||||
(0!64) as f32 / 1000 as f32
|
||||
@@ -29,11 +33,9 @@ export fn random() -> i32 {
|
||||
}
|
||||
|
||||
export fn random64() -> i64 {
|
||||
let state: i64;
|
||||
randomState = (state := (
|
||||
state := randomState ^ (randomState #>> 12i64)
|
||||
) ^ (state << 25i64)
|
||||
) ^ (state #>> 27i64);
|
||||
let lazy state = randomState ^ (randomState #>> 12i64);
|
||||
let lazy state = state ^ (state << 25i64);
|
||||
randomState = state ^ (state #>> 27i64);
|
||||
randomState * 0x2545f4914f6cdd1di64
|
||||
}
|
||||
|
||||
@@ -59,12 +61,9 @@ export fn cls(col: i32) {
|
||||
let i: i32;
|
||||
textCursorX = 0;
|
||||
textCursorY = 0;
|
||||
graphicsText = 0;
|
||||
col = (col & 255) * 0x1010101;
|
||||
loop pixels {
|
||||
i!120 = col;
|
||||
branch_if (i := i + 4) < 320*240: pixels;
|
||||
}
|
||||
outputChannel = 0;
|
||||
textScale = 1;
|
||||
memory.fill(120, col, 320*240);
|
||||
}
|
||||
|
||||
export fn setPixel(x: i32, y: i32, col: i32) {
|
||||
@@ -88,12 +87,70 @@ fn clamp(v: i32, min: i32, max: i32) -> i32 {
|
||||
export fn hline(x1: i32, x2: i32, y: i32, col: i32) {
|
||||
x1 = clamp(x1, 0, 320);
|
||||
x2 = clamp(x2, 0, 320);
|
||||
if x1 < x2 & y #< 240 {
|
||||
if y #>= 240 {
|
||||
return;
|
||||
}
|
||||
let word_start = (x1 + 3) & -4;
|
||||
let word_end = x2 & -4;
|
||||
if word_end > word_start {
|
||||
col = (col & 255) * 0x1010101;
|
||||
let ptr = y * 320 + x1;
|
||||
let end = ptr + word_start - x1;
|
||||
if ptr + 2 <= end {
|
||||
ptr?120 = col;
|
||||
ptr?121 = col;
|
||||
ptr += 2;
|
||||
}
|
||||
if ptr < end {
|
||||
ptr?120 = col;
|
||||
ptr += 1;
|
||||
}
|
||||
end += word_end - word_start;
|
||||
loop words {
|
||||
if ptr + 16 <= end {
|
||||
ptr!120 = col;
|
||||
ptr!124 = col;
|
||||
ptr!128 = col;
|
||||
ptr!132 = col;
|
||||
ptr += 16;
|
||||
branch words;
|
||||
}
|
||||
if ptr + 8 <= end {
|
||||
ptr!120 = col;
|
||||
ptr!124 = col;
|
||||
ptr += 8;
|
||||
}
|
||||
if ptr < end {
|
||||
ptr!120 = col;
|
||||
ptr += 4;
|
||||
}
|
||||
}
|
||||
end += x2 - word_end;
|
||||
if ptr + 2 <= end {
|
||||
ptr?120 = col;
|
||||
ptr?121 = col;
|
||||
ptr += 2;
|
||||
}
|
||||
if ptr < end {
|
||||
ptr?120 = col;
|
||||
}
|
||||
} else {
|
||||
let ptr = y * 320 + x1;
|
||||
let end = ptr + x2 - x1;
|
||||
loop pixels {
|
||||
if ptr + 4 <= end {
|
||||
ptr?120 = col;
|
||||
ptr?121 = col;
|
||||
ptr?122 = col;
|
||||
ptr?123 = col;
|
||||
ptr += 4;
|
||||
}
|
||||
if ptr + 2 <= end {
|
||||
ptr?120 = col;
|
||||
ptr?121 = col;
|
||||
ptr += 2;
|
||||
}
|
||||
if ptr < end {
|
||||
ptr?120 = col;
|
||||
branch_if (ptr := ptr + 1) < end: pixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +172,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 xr = nearest(x + w) as i32;
|
||||
let yt = nearest(y) as i32;
|
||||
@@ -156,7 +213,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 y = clamp(nearest(cy - radius) as i32, -1, 241);
|
||||
let maxY = clamp(nearest(cy + radius) as i32, -1, 241);
|
||||
@@ -256,6 +313,11 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||
p = y1;
|
||||
}
|
||||
|
||||
if max_axis == 0 as f32 {
|
||||
setPixel(x1 as i32, y1 as i32, col);
|
||||
return;
|
||||
}
|
||||
|
||||
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
||||
p = floor(p) + 0.5 - p;
|
||||
if max_axis < 0 as f32 {
|
||||
@@ -267,7 +329,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||
dy = dy / max_axis;
|
||||
|
||||
let f = min(max_axis, max(0 as f32, p));
|
||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
||||
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
|
||||
|
||||
if !steps {
|
||||
return;
|
||||
@@ -280,7 +342,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||
|
||||
loop pixels {
|
||||
if steps := steps - 1 {
|
||||
setPixel(x1 as i32, y1 as i32, col);
|
||||
setPixel(i32.trunc_sat_f32_s(x1), i32.trunc_sat_f32_s(y1), col);
|
||||
x1 = x1 + dx;
|
||||
y1 = y1 + dy;
|
||||
branch pixels;
|
||||
@@ -288,7 +350,93 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||
}
|
||||
|
||||
f = min(max_axis, p) - p;
|
||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, 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;
|
||||
}
|
||||
}
|
||||
|
||||
//////////
|
||||
@@ -299,7 +447,8 @@ global mut textCursorX = 0;
|
||||
global mut textCursorY = 0;
|
||||
global mut textColor = 15;
|
||||
global mut bgColor = 0;
|
||||
global mut graphicsText = 0;
|
||||
global mut outputChannel = 0;
|
||||
global mut textScale = 1;
|
||||
|
||||
export fn printChar(char: i32) {
|
||||
loop chars {
|
||||
@@ -311,6 +460,13 @@ export fn printChar(char: i32) {
|
||||
global mut controlCodeLength = 0;
|
||||
|
||||
fn printSingleChar(char: i32) {
|
||||
let charSize = 8 * textScale;
|
||||
|
||||
if outputChannel >= 2 & (char < 4 | char > 6) {
|
||||
logChar(char);
|
||||
return;
|
||||
}
|
||||
|
||||
controlCodeLength?0x12d20 = char;
|
||||
controlCodeLength = controlCodeLength + 1;
|
||||
char = 0x12d20?0;
|
||||
@@ -324,54 +480,61 @@ fn printSingleChar(char: i32) {
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 4 | char == 5 {
|
||||
graphicsText = char == 5;
|
||||
if char >= 4 & char <= 6 {
|
||||
outputChannel = char - 4;
|
||||
if !outputChannel {
|
||||
textCursorX = 0;
|
||||
textCursorY = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 7 {
|
||||
80?0 = 80?0 ^ 2;
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 8 {
|
||||
textCursorX = textCursorX - 8;
|
||||
if !graphicsText & textCursorX < 0 {
|
||||
textCursorX = 320-8;
|
||||
textCursorX = textCursorX - charSize;
|
||||
if !outputChannel & textCursorX < 0 {
|
||||
textCursorX = 320-charSize;
|
||||
printSingleChar(11);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 9 {
|
||||
if !graphicsText & textCursorX >= 320 {
|
||||
if !outputChannel & textCursorX >= 320 {
|
||||
printChar(0xd0a);
|
||||
}
|
||||
textCursorX = textCursorX + 8;
|
||||
textCursorX = textCursorX + charSize;
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 10 {
|
||||
textCursorY = textCursorY + 8;
|
||||
if !graphicsText & textCursorY >= 240 {
|
||||
textCursorY = 240 - 8;
|
||||
textCursorY = textCursorY + charSize;
|
||||
if !outputChannel & textCursorY >= 240 {
|
||||
textCursorY = 240 - charSize;
|
||||
let i: i32;
|
||||
loop scroll_copy {
|
||||
i!120 = i!(120 + 320 * 8);
|
||||
branch_if (i := i + 4) < 320 * (240 - 8): scroll_copy;
|
||||
i!120 = (i + 320 * charSize)!120;
|
||||
branch_if (i := i + 4) < 320 * (240 - charSize): scroll_copy;
|
||||
}
|
||||
rectangle(0 as f32, (240 - 8) as f32, 320 as f32, 8 as f32, bgColor);
|
||||
rectangle(0 as f32, (240 - charSize) as f32, 320 as f32, charSize as f32, bgColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 11 {
|
||||
textCursorY = textCursorY - 8;
|
||||
if !graphicsText & textCursorY < 0 {
|
||||
textCursorY = textCursorY - charSize;
|
||||
if !outputChannel & textCursorY < 0 {
|
||||
textCursorY = 0;
|
||||
let i = 320 * (240 - 8);
|
||||
let i = 320 * (240 - charSize);
|
||||
loop scroll_copy {
|
||||
i!(116 + 320 * 8) = i!116;
|
||||
(i + 320 * charSize)!116 = i!116;
|
||||
branch_if (i := i - 4): scroll_copy;
|
||||
}
|
||||
rectangle(0 as f32, 0 as f32, 320 as f32, 8 as f32, bgColor);
|
||||
rectangle(0 as f32, 0 as f32, 320 as f32, charSize as f32, bgColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -403,9 +566,15 @@ fn printSingleChar(char: i32) {
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 30 {
|
||||
let scale = 0x12d20?1;
|
||||
textScale = select(scale > 0 & scale <= 16, scale, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 31 {
|
||||
textCursorX = 0x12d20?1 * (8 - graphicsText * 6);
|
||||
textCursorY = 0x12d20?2 * (8 - graphicsText * 7);
|
||||
textCursorX = 0x12d20?1 * (8 - outputChannel * 6);
|
||||
textCursorY = 0x12d20?2 * (8 - outputChannel * 7);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,35 +594,37 @@ data(0x12d00) {
|
||||
1, 1, 1, 1, // 16-19,
|
||||
1, 1, 1, 1, // 20-23,
|
||||
1, 1, 1, 1, // 24-27,
|
||||
1, 1, 1, 3 // 28-31
|
||||
1, 1, 2, 3 // 28-31
|
||||
)
|
||||
}
|
||||
|
||||
fn drawChar(char: i32) {
|
||||
if !graphicsText & textCursorX >= 320 {
|
||||
if !outputChannel & textCursorX >= 320 {
|
||||
printChar(0xd0a);
|
||||
}
|
||||
|
||||
let charSize = 8 * textScale;
|
||||
|
||||
let y: i32;
|
||||
loop rows {
|
||||
let bits = (char * 8 + y)?0x13400;
|
||||
let bits = (char * 8 + y / textScale)?0x13400;
|
||||
let x = 0;
|
||||
if graphicsText {
|
||||
if outputChannel {
|
||||
loop pixels {
|
||||
if (bits := bits << 1) & 256 {
|
||||
if (bits << (x / textScale)) & 128 {
|
||||
setPixel(textCursorX + x, textCursorY + y, textColor);
|
||||
}
|
||||
branch_if (x := x + 1) < 8: pixels;
|
||||
branch_if (x := x + 1) < charSize: pixels;
|
||||
}
|
||||
} else {
|
||||
loop pixels {
|
||||
setPixel(textCursorX + x, textCursorY + y, select((bits := bits << 1) & 256, textColor, bgColor));
|
||||
branch_if (x := x + 1) < 8: pixels;
|
||||
setPixel(textCursorX + x, textCursorY + y, select((bits << (x / textScale)) & 128, textColor, bgColor));
|
||||
branch_if (x := x + 1) < charSize: pixels;
|
||||
}
|
||||
}
|
||||
branch_if (y := y + 1) < 8: rows;
|
||||
branch_if (y := y + 1) < charSize: rows;
|
||||
}
|
||||
textCursorX = textCursorX + 8;
|
||||
textCursorX = textCursorX + charSize;
|
||||
}
|
||||
|
||||
export fn printString(ptr: i32) {
|
||||
@@ -490,11 +661,37 @@ export fn setBackgroundColor(col: i32) {
|
||||
}
|
||||
|
||||
export fn setCursorPosition(x: i32, y: i32) {
|
||||
let lazy scale = select(graphicsText, 1, 8);
|
||||
let lazy scale = select(outputChannel, 1, 8);
|
||||
textCursorX = x * scale;
|
||||
textCursorY = y * scale;
|
||||
}
|
||||
|
||||
///////////
|
||||
// SOUND //
|
||||
///////////
|
||||
|
||||
include "ges.cwa"
|
||||
|
||||
export fn playNote(channel: i32, note: i32) {
|
||||
(channel * 6)?80 = (channel * 6)?80 & 0xfe ^ if note {
|
||||
(channel * 6)?83 = note & 127;
|
||||
2 | !(note >> 7)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
data 80 {
|
||||
i8(
|
||||
0x80, 0xc0, 0, 81, 0xa0, 0x50,
|
||||
0xc4, 0, 0, 69, 0x60, 0x40,
|
||||
0x44, 0xb0, 0, 69, 0x90, 0x43,
|
||||
0x4, 0xf0, 0, 69, 0xa4, 0x44,
|
||||
0xff, 0xff,
|
||||
1, 1, 0, 100, 0, 100
|
||||
)
|
||||
}
|
||||
|
||||
///////////
|
||||
// SETUP //
|
||||
///////////
|
||||
@@ -535,10 +732,19 @@ start fn setup() {
|
||||
branch_if (i := i - 1) >= 0: expand_sweetie;
|
||||
}
|
||||
|
||||
memory.fill(0, 0, 64);
|
||||
memory.fill(112, 0, 8);
|
||||
memory.fill(0x14000, 0, 0x2c000);
|
||||
|
||||
|
||||
cls(0);
|
||||
randomSeed(random());
|
||||
}
|
||||
|
||||
data 0x12c78 {
|
||||
i32(80)
|
||||
}
|
||||
|
||||
data 0x13000+192*4 {
|
||||
i32(
|
||||
0x2c1c1a,
|
||||
|
||||
@@ -36,6 +36,8 @@ for dir in build/*; do
|
||||
cp $example $dir/examples
|
||||
done
|
||||
|
||||
cp -r ../examples/include $dir/include
|
||||
|
||||
mkdir $dir/carts
|
||||
for example in $dir/examples/*; do
|
||||
build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8
|
||||
|
||||
@@ -14,12 +14,14 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
|
||||
* Memory: 256KB
|
||||
* Gamepad input (D-Pad + 4 Buttons)
|
||||
|
||||
For detailed [documentation see here](docs).
|
||||
|
||||
## Examples
|
||||
* [Fireworks](v0.1.1#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
||||
* [Skip Ahead](v0.1.1#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
|
||||
* [OhNoAnotherTunnel](v0.1.1#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
||||
* [Technotunnel](v0.1.1#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
||||
* [Font & Palette](v0.1.1#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
||||
* [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.
|
||||
* [OhNoAnotherTunnel](v0.2.0#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
||||
* [Technotunnel](v0.2.0#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
||||
* [Font & Palette](v0.2.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
||||
|
||||
Examplers for older versions:
|
||||
|
||||
@@ -29,27 +31,20 @@ Examplers for older versions:
|
||||
|
||||
## Versions
|
||||
|
||||
### v0.1.1
|
||||
### v0.4.0
|
||||
|
||||
* [Web runtime](v0.1.1)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip)
|
||||
* [Web runtime](../v0.4.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* implement more robust file watcher
|
||||
* add basic video recording on F10 in web runtime
|
||||
* add screenshot on F9
|
||||
* add watchdog to interrupt hanging update in native runtime
|
||||
* add devkit mode to web runtime
|
||||
* add unpack and compile commands to uw8
|
||||
* add support for table/element section in pack command
|
||||
* disable wayland support (caused missing window decorations in gnome)
|
||||
* add support for sound on mono- and surround-only devices
|
||||
* update wasmtime dependency to fix performance regression in 0.3.0
|
||||
* add frame counter since module start at location 72
|
||||
* add 6 and 7 parameter function types to base module
|
||||
|
||||
### v0.1.0
|
||||
### Older versions
|
||||
|
||||
* [Web runtime](v0.1.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
||||
[Find older versions here.](versions)
|
||||
|
||||
@@ -5,22 +5,28 @@ description = "Docs"
|
||||
|
||||
# Overview
|
||||
|
||||
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export
|
||||
MicroW8 loads WebAssembly modules with a maximum size of 256kb. Your module needs to export
|
||||
a function `fn upd()` which will be called once per frame.
|
||||
After calling `upd` MicroW8 will display the 320x240 8bpp framebuffer located
|
||||
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).
|
||||
|
||||
If the module exports a function called `start`, it will be called once after the module is
|
||||
loaded.
|
||||
|
||||
# Memory map
|
||||
|
||||
```
|
||||
00000-00040: user memory
|
||||
00040-00044: time since module start in ms
|
||||
00044-0004c: gamepad state
|
||||
0004c-00078: reserved
|
||||
0004c-00050: number of frames since module start
|
||||
00050-00070: sound data (synced to sound thread)
|
||||
00070-00078: reserved
|
||||
00078-12c78: frame buffer
|
||||
12c78-13000: reserved
|
||||
12c78-12c7c: sound registers/work area base address (for sndGes function)
|
||||
12c7c-13000: reserved
|
||||
13000-13400: palette
|
||||
13400-13c00: font
|
||||
13c00-14000: reserved
|
||||
@@ -47,7 +53,7 @@ Returns the arccosine of `x`.
|
||||
|
||||
Returns the arctangent of `x`.
|
||||
|
||||
### fn atan2(y: f32, y: f32) -> f32
|
||||
### fn atan2(y: f32, x: f32) -> f32
|
||||
|
||||
Returns the angle between the point `(x, y)` and the positive x-axis.
|
||||
|
||||
@@ -140,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.)
|
||||
|
||||
### 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 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.
|
||||
|
||||
@@ -156,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.
|
||||
|
||||
### 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
|
||||
|
||||
MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.
|
||||
@@ -217,19 +238,27 @@ When printing characters only the foreground pixels are set, the background is "
|
||||
|
||||
Moving/printing past any border does not cause any special operation, the cursor just goes off-screen.
|
||||
|
||||
### Text scale
|
||||
|
||||
An integer text scale factor in the range 1x-16x can be set with control char 30. An attempt to
|
||||
set a scale outside that range will reset the scale to 1x.
|
||||
|
||||
After startup and `cls` the scale is initialized to 1x.
|
||||
|
||||
### Control chars
|
||||
|
||||
Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
|
||||
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.
|
||||
|
||||
| Code | Parameters | Operation |
|
||||
| ----- | ---------- | ------------------------------------ |
|
||||
| ----- | ---------- | ------------------------------------------ |
|
||||
| 0 | - | Nop |
|
||||
| 1 | char | Print char (including control chars) |
|
||||
| 2-3 | - | Reserved |
|
||||
| 4 | - | Switch to normal mode |
|
||||
| 4 | - | Switch to normal mode, reset cursor to 0,0 |
|
||||
| 5 | - | Switch to graphics mode |
|
||||
| 6-7 | - | Reserved |
|
||||
| 6 | - | Switch output to (debug) console |
|
||||
| 7 | - | Bell / trigger sound channel 0 |
|
||||
| 8 | - | Move cursor left |
|
||||
| 9 | - | Move cursor right |
|
||||
| 10 | - | Move cursor down |
|
||||
@@ -240,11 +269,27 @@ Avoid the reserved control chars, they are currently NOPs but their behavior can
|
||||
| 15 | color | Set the text color |
|
||||
| 16-23 | - | Reserved |
|
||||
| 24 | - | Swap text/background colors |
|
||||
| 25-30 | - | Reserved |
|
||||
| 25-29 | - | Reserved |
|
||||
| 30 | scale | Set text scale (1-16) |
|
||||
| 31 | x, y | Set cursor position (*) |
|
||||
|
||||
(*) In graphics mode, the x coordinate is doubled when using control char 31 to be able to cover the whole screen with one byte.
|
||||
|
||||
#### Debug output
|
||||
|
||||
Control code 6 switches all text output (except codes 4 and 5 to switch output back to the screen) to the console. Where exactly this ends
|
||||
up (if at all) is an implementation detail of the runtimes. The native dev-runtime writes the debug output to `stdout`, the web runtime to
|
||||
the debug console using `console.log`. Both implementations buffer the output until they encounter a newline character (10) in the output stream.
|
||||
|
||||
There may be future runtimes that ignore the debug output completely.
|
||||
|
||||
In CurlyWas, a simple way to log some value might look like this:
|
||||
```
|
||||
printChar('\06V: '); // switch to console out, print some prefix
|
||||
printInt(some_value);
|
||||
printChar('\n\4'); // newline and switch back to screen
|
||||
```
|
||||
|
||||
### fn printChar(char: i32)
|
||||
|
||||
Prints the character in the lower 8 bits of `char`. If the upper 24 bits are non-zero, right-shifts `char` by 8 bits and loops back to the beginning.
|
||||
@@ -269,6 +314,130 @@ Sets the background color.
|
||||
|
||||
Sets the cursor position. In normal mode `x` and `y` are multiplied by 8 to get the pixel position, in graphics mode they are used as is.
|
||||
|
||||
## Sound
|
||||
|
||||
### Low level operation
|
||||
|
||||
MicroW8 actually runs two instances of your module. On the first instance, it calls `upd` and displays the framebuffer found in its memory. On the
|
||||
second instance, it calls `snd` instead with an incrementing sample index and expects that function to return sound samples for the left and right
|
||||
channel at 44100 Hz. If your module does not export a `snd` function, it calls the api function `sndGes` instead.
|
||||
|
||||
As the only means of communication, 32 bytes starting at address 0x00050 are copied from main to sound memory after `upd` returns.
|
||||
|
||||
By default, the `sndGes` function generates sound based on the 32 bytes at 0x00050. This means that in the default configuration those 32 bytes act
|
||||
as sound registers. See the `sndGes` function for the meaning of those registers.
|
||||
|
||||
### export fn snd(sampleIndex: i32) -> f32
|
||||
|
||||
If the module exports a `snd` function, it is called 88200 times per second to provide PCM sample data for playback (44.1kHz stereo).
|
||||
The `sampleIndex` will start at 0 and increments by 1 for each call. On even indices the function is expected to return a sample value for
|
||||
the left channel, on odd indices for the right channel.
|
||||
|
||||
### fn playNote(channel: i32, note: i32)
|
||||
|
||||
Triggers a note (1-127) on the given channel (0-3). Notes are semitones with 69 being A4 (same as MIDI). A note value of 0 stops the
|
||||
sound playing on that channel. A note value 128-255 will trigger note-128 and immediately stop it (playing attack+release parts of envelope).
|
||||
|
||||
This function assumes the default setup, with the `sndGes` registers located at 0x00050.
|
||||
|
||||
### fn sndGes(sampleIndex: i32) -> f32
|
||||
|
||||
This implements a sound chip, generating sound based on 32 bytes of sound registers.
|
||||
|
||||
The spec of this sound chip are:
|
||||
|
||||
- 4 channels with individual volume control (0-15)
|
||||
- rect, saw, tri, noise wave forms selectable per channel
|
||||
- each wave form supports some kind of pulse width modulation
|
||||
- each channel has an optional automatic low pass filter, or can be sent to one of two manually controllable filters
|
||||
- each channel can select between a narrow and a wide stereo positioning. The two stereo positions of each channel are fixed.
|
||||
- optional ring modulation
|
||||
|
||||
This function requires 1024 bytes of working memory, the first 32 bytes of which are interpreted as the sound registers.
|
||||
The base address of its working memory can be configured by writing the address to 0x12c78. It defaults to 0x00050.
|
||||
|
||||
Here is a short description of the 32 sound registers.
|
||||
|
||||
```
|
||||
00 - CTRL0
|
||||
06 - CTRL1
|
||||
0c - CTRL2
|
||||
12 - CTRL3
|
||||
| 7 6 | 5 | 4 | 3 2 | 1 | 0 |
|
||||
| wave | ring | wide | filter | trigger | note on |
|
||||
|
||||
note on: stay in decay/sustain part of envelope
|
||||
trigger: the attack part of the envlope is triggered when either this changes
|
||||
or note on is changed from 0 to 1.
|
||||
filter : 0 - no filter
|
||||
1 - fixed 6db 1-pole filter with cutoff two octaves above note
|
||||
2 - programmable filter 0
|
||||
3 - programmable filter 1
|
||||
wide : use wide stereo panning
|
||||
ring : ring modulate with triangle wave at frequency of previous channel
|
||||
wave : 0 - rectangle
|
||||
1 - saw
|
||||
2 - triangle
|
||||
3 - noise
|
||||
|
||||
01 - PULS0
|
||||
07 - PULS1
|
||||
0d - PULS2
|
||||
13 - PULS3
|
||||
Pulse width 0-255, with 0 being the plain version of each wave form.
|
||||
rectangle - 50%-100% pulse width
|
||||
saw - inverts 0%-100% of the saw wave form around the center
|
||||
triangle - morphs into an octave up triangle wave
|
||||
noise - blends into a decimated saw wave (just try it out)
|
||||
|
||||
02 - FINE0
|
||||
08 - FINE1
|
||||
0e - FINE2
|
||||
14 - FINE3
|
||||
Fractional note
|
||||
|
||||
03 - NOTE0
|
||||
09 - NOTE1
|
||||
0f - NOTE2
|
||||
15 - NOTE3
|
||||
Note, 69 = A4
|
||||
|
||||
04 - ENVA0
|
||||
0a - ENVA1
|
||||
10 - ENVA2
|
||||
16 - ENVA3
|
||||
| 7 6 5 4 | 3 2 1 0 |
|
||||
| decay | attack |
|
||||
|
||||
05 - ENVB0
|
||||
0b - ENVB1
|
||||
11 - ENVB2
|
||||
17 - ENVB3
|
||||
| 7 6 5 4 | 3 2 1 0 |
|
||||
| release | sustain |
|
||||
|
||||
18 - VO01
|
||||
| 7 6 5 4 | 3 2 1 0 |
|
||||
| volume 1 | volume 0 |
|
||||
|
||||
19 - VO23
|
||||
| 7 6 5 4 | 3 2 1 0 |
|
||||
| volume 3 | volume 2 |
|
||||
|
||||
1a - FCTR0
|
||||
1b - FCTR1
|
||||
| 7 6 5 4 | 3 | 2 | 1 | 0 |
|
||||
| resonance | 0 | band | high | low |
|
||||
|
||||
1c - FFIN0
|
||||
1e - FFIN1
|
||||
cutoff frequency - fractional note
|
||||
|
||||
1d - FNOT0
|
||||
1f - FNOT1
|
||||
cutoff frequency - note
|
||||
```
|
||||
|
||||
# The `uw8` tool
|
||||
|
||||
The `uw8` tool included in the MicroW8 download includes a number of useful tools for developing MicroW8 carts. For small productions written in
|
||||
@@ -284,6 +453,7 @@ Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (W
|
||||
|
||||
Options:
|
||||
|
||||
* `-b`, `--browser`: Run in browser instead of using native runtime
|
||||
* `-t FRAMES`, `--timeout FRAMES`: Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
|
||||
and execution of the cart is stopped. Defaults to 30 (0.5s)
|
||||
* `-w`, `--watch`: Reloads the given file every time it changes on disk.
|
||||
@@ -292,6 +462,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.
|
||||
* `-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`
|
||||
|
||||
Usage:
|
||||
@@ -400,7 +594,7 @@ a base module provided by MicroW8.
|
||||
|
||||
You can generate this base module yourself using
|
||||
`uw8-tool`. As a quick summary, it provides all function
|
||||
types with up to 5 parameters (i32 or f32) where the
|
||||
types with up to 7 parameters (i32 or f32) where the
|
||||
`f32` parameters always preceed the `i32` parameters.
|
||||
Then it includes all imports that MicroW8 provides,
|
||||
a function section with a single function of type
|
||||
|
||||
160
site/content/versions.md
Normal file
160
site/content/versions.md
Normal file
@@ -0,0 +1,160 @@
|
||||
+++
|
||||
description = "Versions"
|
||||
+++
|
||||
|
||||
### v0.4.0
|
||||
|
||||
* [Web runtime](../v0.4.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.4.0/microw8-0.4.0-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* add support for sound on mono- and surround-only devices
|
||||
* update wasmtime dependency to fix performance regression in 0.3.0
|
||||
* add frame counter since module start at location 72
|
||||
* add 6 and 7 parameter function types to base module
|
||||
|
||||
### v0.3.0
|
||||
|
||||
* [Web runtime](../v0.3.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.3.0/microw8-0.3.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.3.0/microw8-0.3.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.3.0/microw8-0.3.0-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* add blitSprite and grabSprite API calls
|
||||
* add support for integer scaling up to 16x for printing text
|
||||
* fix incompatibility with sound devices only offering 16bit audio formats
|
||||
* add support for br_table instruction in packed carts
|
||||
|
||||
### 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
|
||||
|
||||
* [Web runtime](../v0.2.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* [add sound support!](docs#sound)
|
||||
* add support to redirect text output to the console for debugging using control code 6
|
||||
* update curlywas:
|
||||
* add support for `else if`
|
||||
* add support for escape sequences in strings
|
||||
* add support for char literals
|
||||
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||
|
||||
### v0.2.0-rc3
|
||||
|
||||
* [Web runtime](../v0.2.0-rc3)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
|
||||
consecutive frame numbers, provided the module can be run at 60 fps
|
||||
* add support to redirect text output to the console for debugging using control code 6
|
||||
* update curlywas:
|
||||
* add support for `else if`
|
||||
* add support for escape sequences in strings
|
||||
* add support for char literals
|
||||
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||
|
||||
### v0.2.0-rc2
|
||||
|
||||
* [Web runtime](../v0.2.0-rc2)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* fix timing issues of sound playback, especially on systems with large sound buffers
|
||||
|
||||
### v0.2.0-rc1
|
||||
|
||||
* [Web runtime](../v0.2.0-rc1)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* [add sound support](docs#sound)
|
||||
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||
|
||||
Known issues:
|
||||
|
||||
* timing accuracy/update frequency of sound support currently depends on sound buffer size
|
||||
|
||||
### v0.1.2
|
||||
|
||||
* [Web runtime](../v0.1.2)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* add option to `uw8 run` to run the cart in the browser using the web runtime
|
||||
*../ CurlyWas: implement `include` support
|
||||
* CurlyWas: implement support for constants
|
||||
* fix crash when trying to draw zero sized line
|
||||
|
||||
### v0.1.1
|
||||
|
||||
* [Web runtime](../v0.1.1)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip)
|
||||
|
||||
Changes:
|
||||
|
||||
* implement more robust file watcher
|
||||
* add basic video recording on F10 in web runtime
|
||||
*../ add screenshot on F9
|
||||
* add watchdog to interrupt hanging update in native runtime
|
||||
* add devkit mode to web runtime
|
||||
*../ add unpack and compile commands to uw8
|
||||
* add support for table/element section in pack command
|
||||
* disable wayland support (caused missing window decorations in gnome)
|
||||
|
||||
### v0.1.0
|
||||
|
||||
* [Web runtime](../v0.1.0)
|
||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
||||
1
site/static/v0.1.2/index.html
Normal file
1
site/static/v0.1.2/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0-rc1/index.html
Normal file
1
site/static/v0.2.0-rc1/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0-rc2/index.html
Normal file
1
site/static/v0.2.0-rc2/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0-rc3/index.html
Normal file
1
site/static/v0.2.0-rc3/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0/index.html
Normal file
1
site/static/v0.2.0/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.1/index.html
Normal file
1
site/static/v0.2.1/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.2/index.html
Normal file
1
site/static/v0.2.2/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.3.0/index.html
Normal file
1
site/static/v0.3.0/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.4.0/index.html
Normal file
1
site/static/v0.4.0/index.html
Normal file
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
<section>
|
||||
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
||||
</section>
|
||||
<a href="v0.1.1">
|
||||
<a href="v0.4.0">
|
||||
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +1,67 @@
|
||||
use anyhow::{bail, Result};
|
||||
use notify::{DebouncedEvent, Watcher, RecommendedWatcher};
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
path::{Path, PathBuf},
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use notify_debouncer_mini::{
|
||||
new_debouncer,
|
||||
notify::{self, RecommendedWatcher},
|
||||
DebouncedEvent, DebouncedEventKind, Debouncer,
|
||||
};
|
||||
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
||||
|
||||
pub struct FileWatcher {
|
||||
_watcher: RecommendedWatcher,
|
||||
debouncer: Debouncer<RecommendedWatcher>,
|
||||
watched_files: BTreeSet<PathBuf>,
|
||||
directories: BTreeSet<PathBuf>,
|
||||
rx: mpsc::Receiver<DebouncedEvent>,
|
||||
}
|
||||
|
||||
pub struct FileWatcherBuilder(BTreeSet<PathBuf>);
|
||||
|
||||
impl FileWatcher {
|
||||
pub fn builder() -> FileWatcherBuilder {
|
||||
FileWatcherBuilder(BTreeSet::new())
|
||||
pub fn new() -> Result<FileWatcher> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let debouncer = new_debouncer(Duration::from_millis(100), move |res| match res {
|
||||
Ok(events) => {
|
||||
for event in events {
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error watching for file changes: {err}");
|
||||
}
|
||||
})?;
|
||||
Ok(FileWatcher {
|
||||
debouncer,
|
||||
watched_files: BTreeSet::new(),
|
||||
directories: BTreeSet::new(),
|
||||
rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
|
||||
let path = path.into();
|
||||
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
|
||||
|
||||
if !self.directories.contains(parent) {
|
||||
self.debouncer
|
||||
.watcher()
|
||||
.watch(parent, notify::RecursiveMode::NonRecursive)?;
|
||||
self.directories.insert(parent.to_path_buf());
|
||||
}
|
||||
|
||||
self.watched_files.insert(path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
||||
let event = self.rx.try_recv();
|
||||
match event {
|
||||
Ok(DebouncedEvent::Create(path) | DebouncedEvent::Write(path)) => {
|
||||
let handle = same_file::Handle::from_path(&path)?;
|
||||
match self.rx.try_recv() {
|
||||
Ok(event) => match event.kind {
|
||||
DebouncedEventKind::Any => {
|
||||
let handle = same_file::Handle::from_path(&event.path)?;
|
||||
for file in &self.watched_files {
|
||||
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"),
|
||||
_ => (),
|
||||
}
|
||||
@@ -38,33 +69,3 @@ impl FileWatcher {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWatcherBuilder {
|
||||
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
|
||||
self.0.insert(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<FileWatcher> {
|
||||
let mut directories: BTreeSet<&Path> = BTreeSet::new();
|
||||
|
||||
for file in &self.0 {
|
||||
if let Some(directory) = file.parent() {
|
||||
directories.insert(directory);
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
||||
|
||||
for directory in directories {
|
||||
watcher.watch(directory, notify::RecursiveMode::NonRecursive)?;
|
||||
}
|
||||
|
||||
Ok(FileWatcher {
|
||||
_watcher: watcher,
|
||||
watched_files: self.0,
|
||||
rx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ use anyhow::Result;
|
||||
|
||||
pub trait Runtime {
|
||||
fn is_open(&self) -> bool;
|
||||
fn set_timeout(&mut self, _timeout: u32) {
|
||||
eprintln!("Warning: runtime doesn't support timeout");
|
||||
}
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||
fn run_frame(&mut self) -> Result<()>;
|
||||
}
|
||||
151
src/main.rs
151
src/main.rs
@@ -13,8 +13,13 @@ use uw8::RunWebServer;
|
||||
use uw8::Runtime;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default()).init();
|
||||
let mut args = Arguments::from_env();
|
||||
|
||||
// try to enable ansi support in win10 cmd shell
|
||||
#[cfg(target_os = "windows")]
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
|
||||
match args.subcommand()?.as_deref() {
|
||||
Some("version") => {
|
||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||
@@ -48,6 +53,7 @@ fn main() -> Result<()> {
|
||||
#[cfg(any(feature = "native", feature = "browser"))]
|
||||
fn run(mut args: Arguments) -> Result<()> {
|
||||
let watch_mode = args.contains(["-w", "--watch"]);
|
||||
#[allow(unused)]
|
||||
let timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
|
||||
|
||||
let mut config = Config::default();
|
||||
@@ -75,15 +81,21 @@ fn run(mut args: Arguments) -> Result<()> {
|
||||
#[cfg(not(feature = "native"))]
|
||||
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||
|
||||
#[allow(unused)]
|
||||
let disable_audio = args.contains(["-m", "--no-audio"]);
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
let window_config = {
|
||||
let mut config = 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 mut watcher = uw8::FileWatcher::builder();
|
||||
|
||||
if watch_mode {
|
||||
watcher.add_file(&filename);
|
||||
}
|
||||
|
||||
let watcher = watcher.build()?;
|
||||
let mut watcher = uw8::FileWatcher::new()?;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
@@ -91,7 +103,13 @@ fn run(mut args: Arguments) -> Result<()> {
|
||||
#[cfg(not(feature = "native"))]
|
||||
unimplemented!();
|
||||
#[cfg(feature = "native")]
|
||||
Box::new(MicroW8::new()?)
|
||||
{
|
||||
let mut microw8 = MicroW8::new(timeout, window_config)?;
|
||||
if disable_audio {
|
||||
microw8.disable_audio();
|
||||
}
|
||||
Box::new(microw8)
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "browser"))]
|
||||
unimplemented!();
|
||||
@@ -99,22 +117,23 @@ fn run(mut args: Arguments) -> Result<()> {
|
||||
Box::new(RunWebServer::new())
|
||||
};
|
||||
|
||||
if let Some(timeout) = timeout {
|
||||
runtime.set_timeout(timeout);
|
||||
}
|
||||
let mut first_run = true;
|
||||
|
||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||
while runtime.is_open() {
|
||||
if first_run || watcher.poll_changed_file()?.is_some() {
|
||||
let (result, dependencies) = start_cart(&filename, &mut *runtime, &config);
|
||||
if watch_mode {
|
||||
for dep in dependencies {
|
||||
watcher.add_file(dep)?;
|
||||
}
|
||||
}
|
||||
if let Err(err) = result {
|
||||
eprintln!("Load error: {}", err);
|
||||
if !watch_mode {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
while runtime.is_open() {
|
||||
if watcher.poll_changed_file()?.is_some() {
|
||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
||||
eprintln!("Load error: {}", err);
|
||||
}
|
||||
first_run = false;
|
||||
}
|
||||
|
||||
if let Err(err) = runtime.run_frame() {
|
||||
@@ -134,22 +153,33 @@ struct Config {
|
||||
output_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn load_cart(filename: &Path, config: &Config) -> Result<Vec<u8>> {
|
||||
fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>) {
|
||||
let mut dependencies = Vec::new();
|
||||
fn inner(filename: &Path, config: &Config, dependencies: &mut Vec<PathBuf>) -> Result<Vec<u8>> {
|
||||
let mut cart = match SourceType::of_file(filename)? {
|
||||
SourceType::Binary => {
|
||||
let mut cart = vec![];
|
||||
File::open(filename)?.read_to_end(&mut cart)?;
|
||||
|
||||
if cart[0] >= 10 {
|
||||
let src = String::from_utf8(cart)?;
|
||||
cart = if src.chars().find(|c| !c.is_whitespace()) == Some('(') {
|
||||
wat::parse_str(src)?
|
||||
} else {
|
||||
curlywas::compile_str(&src, filename, curlywas::Options::default())?
|
||||
};
|
||||
cart
|
||||
}
|
||||
SourceType::Wat => {
|
||||
let cart = wat::parse_file(filename)?;
|
||||
cart
|
||||
}
|
||||
SourceType::CurlyWas => {
|
||||
let (module, deps) = curlywas::compile_file(filename, curlywas::Options::default());
|
||||
*dependencies = deps;
|
||||
module?
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref pack_config) = config.pack {
|
||||
cart = uw8_tool::pack(&cart, pack_config)?;
|
||||
println!("packed size: {} bytes", cart.len());
|
||||
println!(
|
||||
"\npacked size: {} bytes ({:.2})",
|
||||
cart.len(),
|
||||
uw8_tool::compressed_size(&cart)
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(ref path) = config.output_path {
|
||||
@@ -159,15 +189,67 @@ fn load_cart(filename: &Path, config: &Config) -> Result<Vec<u8>> {
|
||||
Ok(cart)
|
||||
}
|
||||
|
||||
let result = inner(filename, config, &mut dependencies);
|
||||
|
||||
if dependencies.is_empty() {
|
||||
dependencies.push(filename.to_path_buf());
|
||||
}
|
||||
|
||||
(result, dependencies)
|
||||
}
|
||||
|
||||
enum SourceType {
|
||||
Binary,
|
||||
Wat,
|
||||
CurlyWas,
|
||||
}
|
||||
|
||||
impl SourceType {
|
||||
fn of_file(filename: &Path) -> Result<SourceType> {
|
||||
if let Some(extension) = filename.extension() {
|
||||
if extension == "uw8" || extension == "wasm" {
|
||||
return Ok(SourceType::Binary);
|
||||
} else if extension == "wat" || extension == "wast" {
|
||||
return Ok(SourceType::Wat);
|
||||
} else if extension == "cwa" {
|
||||
return Ok(SourceType::CurlyWas);
|
||||
}
|
||||
}
|
||||
|
||||
let mut cart = vec![];
|
||||
File::open(filename)?.read_to_end(&mut cart)?;
|
||||
|
||||
let ty = if cart[0] < 10 {
|
||||
SourceType::Binary
|
||||
} else {
|
||||
let src = String::from_utf8(cart)?;
|
||||
if src.chars().find(|&c| !c.is_whitespace() && c != ';') == Some('(') {
|
||||
SourceType::Wat
|
||||
} else {
|
||||
SourceType::CurlyWas
|
||||
}
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native", feature = "browser"))]
|
||||
fn start_cart(filename: &Path, runtime: &mut dyn Runtime, config: &Config) -> Result<()> {
|
||||
let cart = load_cart(filename, config)?;
|
||||
fn start_cart(
|
||||
filename: &Path,
|
||||
runtime: &mut dyn Runtime,
|
||||
config: &Config,
|
||||
) -> (Result<()>, Vec<PathBuf>) {
|
||||
let (cart, dependencies) = load_cart(filename, config);
|
||||
let cart = match cart {
|
||||
Ok(cart) => cart,
|
||||
Err(err) => return (Err(err), dependencies),
|
||||
};
|
||||
|
||||
if let Err(err) = runtime.load(&cart) {
|
||||
eprintln!("Load error: {}", err);
|
||||
Err(err)
|
||||
(Err(err), dependencies)
|
||||
} else {
|
||||
Ok(())
|
||||
(Ok(()), dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +274,8 @@ fn pack(mut args: Arguments) -> Result<()> {
|
||||
pack: Some(pack_config),
|
||||
output_path: None,
|
||||
},
|
||||
)?;
|
||||
)
|
||||
.0?;
|
||||
|
||||
File::create(out_file)?.write_all(&cart)?;
|
||||
|
||||
@@ -215,7 +298,7 @@ fn compile(mut args: Arguments) -> Result<()> {
|
||||
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||
|
||||
let module = curlywas::compile_file(in_file, options)?;
|
||||
let module = curlywas::compile_file(in_file, options).0?;
|
||||
File::create(out_file)?.write_all(&module)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,41 +1,35 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::{thread, time::Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use cpal::traits::*;
|
||||
use rubato::Resampler;
|
||||
use uw8_window::{Window, WindowConfig};
|
||||
use wasmtime::{
|
||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
Engine, Func, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
};
|
||||
|
||||
static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::Up,
|
||||
Key::Down,
|
||||
Key::Left,
|
||||
Key::Right,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::A,
|
||||
Key::S,
|
||||
];
|
||||
|
||||
pub struct MicroW8 {
|
||||
window: Window,
|
||||
stream: Option<cpal::Stream>,
|
||||
engine: Engine,
|
||||
loader_module: Module,
|
||||
window: Window,
|
||||
window_buffer: Vec<u32>,
|
||||
instance: Option<UW8Instance>,
|
||||
disable_audio: bool,
|
||||
module_data: Option<Vec<u8>>,
|
||||
timeout: u32,
|
||||
instance: Option<UW8Instance>,
|
||||
}
|
||||
|
||||
struct UW8Instance {
|
||||
store: Store<()>,
|
||||
memory: Memory,
|
||||
end_frame: TypedFunc<(), ()>,
|
||||
update: TypedFunc<(), ()>,
|
||||
update: Option<TypedFunc<(), ()>>,
|
||||
start_time: Instant,
|
||||
module: Vec<u8>,
|
||||
frame_counter: u32,
|
||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||
sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
|
||||
}
|
||||
|
||||
impl Drop for UW8Instance {
|
||||
@@ -47,66 +41,60 @@ impl Drop for UW8Instance {
|
||||
}
|
||||
|
||||
struct UW8WatchDog {
|
||||
interupt: wasmtime::InterruptHandle,
|
||||
timeout: u32,
|
||||
engine: Engine,
|
||||
stop: bool,
|
||||
}
|
||||
|
||||
impl MicroW8 {
|
||||
pub fn new() -> Result<MicroW8> {
|
||||
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
||||
pub fn new(timeout: Option<u32>, window_config: WindowConfig) -> Result<MicroW8> {
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
||||
if timeout.is_some() {
|
||||
config.epoch_interruption(true);
|
||||
}
|
||||
let engine = wasmtime::Engine::new(&config)?;
|
||||
|
||||
let loader_module =
|
||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
||||
|
||||
let options = WindowOptions {
|
||||
scale: minifb::Scale::X2,
|
||||
scale_mode: minifb::ScaleMode::AspectRatioStretch,
|
||||
resize: true,
|
||||
..Default::default()
|
||||
};
|
||||
let mut window = Window::new("MicroW8", 320, 240, options)?;
|
||||
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
|
||||
let window = Window::new(window_config)?;
|
||||
|
||||
Ok(MicroW8 {
|
||||
window,
|
||||
stream: None,
|
||||
engine,
|
||||
loader_module,
|
||||
window,
|
||||
window_buffer: vec![0u32; 320 * 240],
|
||||
disable_audio: false,
|
||||
module_data: None,
|
||||
timeout: timeout.unwrap_or(0),
|
||||
instance: None,
|
||||
timeout: 30,
|
||||
})
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.instance = None;
|
||||
for v in &mut self.window_buffer {
|
||||
*v = 0;
|
||||
}
|
||||
pub fn disable_audio(&mut self) {
|
||||
self.disable_audio = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Runtime for MicroW8 {
|
||||
fn is_open(&self) -> bool {
|
||||
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
||||
}
|
||||
|
||||
fn set_timeout(&mut self, timeout: u32) {
|
||||
self.timeout = timeout;
|
||||
self.window.is_open()
|
||||
}
|
||||
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||
self.reset();
|
||||
self.stream = None;
|
||||
self.instance = None;
|
||||
|
||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
||||
store.set_epoch_deadline(60);
|
||||
|
||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||
|
||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||
linker.define("env", "memory", memory)?;
|
||||
linker.define(&store, "env", "memory", memory)?;
|
||||
|
||||
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
||||
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
|
||||
let load_uw8 = loader_instance.get_typed_func::<i32, i32>(&mut store, "load_uw8")?;
|
||||
|
||||
let platform_data = include_bytes!("../platform/bin/platform.uw8");
|
||||
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
||||
@@ -119,6 +107,135 @@ impl super::Runtime for MicroW8 {
|
||||
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
||||
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
||||
|
||||
add_native_functions(&mut linker, &mut store)?;
|
||||
|
||||
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
||||
|
||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||
engine: self.engine.clone(),
|
||||
stop: false,
|
||||
}));
|
||||
|
||||
{
|
||||
let watchdog = watchdog.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(17));
|
||||
if let Ok(watchdog) = watchdog.lock() {
|
||||
if watchdog.stop {
|
||||
break;
|
||||
}
|
||||
watchdog.engine.increment_epoch();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let instance = linker.instantiate(&mut store, &module)?;
|
||||
let end_frame = platform_instance.get_typed_func::<(), ()>(&mut store, "endFrame")?;
|
||||
let update = instance.get_typed_func::<(), ()>(&mut store, "upd").ok();
|
||||
|
||||
if let Some(start) = instance.get_typed_func::<(), ()>(&mut store, "start").ok() {
|
||||
start.call(&mut store, ())?;
|
||||
}
|
||||
|
||||
let (sound_tx, stream) = if self.disable_audio {
|
||||
(None, None)
|
||||
} else {
|
||||
match init_sound(&self.engine, &platform_module, &module) {
|
||||
Ok(sound) => {
|
||||
sound.stream.play()?;
|
||||
(Some(sound.tx), Some(sound.stream))
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to init sound: {}", err);
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.instance = Some(UW8Instance {
|
||||
store,
|
||||
memory,
|
||||
end_frame,
|
||||
update,
|
||||
start_time: Instant::now(),
|
||||
frame_counter: 0,
|
||||
watchdog,
|
||||
sound_tx,
|
||||
});
|
||||
self.stream = stream;
|
||||
self.module_data = Some(module_data.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_frame(&mut self) -> Result<()> {
|
||||
let input = self.window.begin_frame();
|
||||
|
||||
if input.reset {
|
||||
if let Some(module_data) = self.module_data.take() {
|
||||
self.load(&module_data)?;
|
||||
}
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let mut result = Ok(());
|
||||
if let Some(mut instance) = self.instance.take() {
|
||||
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 max = now + Duration::from_millis(17);
|
||||
let next_center = now + Duration::from_millis((16 - offset) as u64);
|
||||
next_center.min(max)
|
||||
};
|
||||
|
||||
{
|
||||
let mem = instance.memory.data_mut(&mut instance.store);
|
||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||
mem[68..72].copy_from_slice(&input.gamepads);
|
||||
mem[72..76].copy_from_slice(&instance.frame_counter.to_le_bytes());
|
||||
}
|
||||
|
||||
instance.frame_counter = instance.frame_counter.wrapping_add(1);
|
||||
|
||||
instance.store.set_epoch_deadline(self.timeout as u64);
|
||||
if let Some(ref update) = instance.update {
|
||||
if let Err(err) = update.call(&mut instance.store, ()) {
|
||||
result = Err(err);
|
||||
}
|
||||
}
|
||||
instance.end_frame.call(&mut instance.store, ())?;
|
||||
|
||||
let memory = instance.memory.data(&instance.store);
|
||||
|
||||
let mut sound_regs = [0u8; 32];
|
||||
sound_regs.copy_from_slice(&memory[80..112]);
|
||||
if let Some(ref sound_tx) = instance.sound_tx {
|
||||
let _ = sound_tx.send(RegisterUpdate {
|
||||
time,
|
||||
data: sound_regs,
|
||||
});
|
||||
}
|
||||
|
||||
let framebuffer_mem = &memory[120..(120 + 320 * 240)];
|
||||
let palette_mem = &memory[0x13000..];
|
||||
self.window
|
||||
.end_frame(framebuffer_mem, palette_mem, next_frame);
|
||||
|
||||
if result.is_ok() {
|
||||
self.instance = Some(instance);
|
||||
}
|
||||
}
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn add_native_functions(
|
||||
linker: &mut wasmtime::Linker<()>,
|
||||
store: &mut wasmtime::Store<()>,
|
||||
) -> Result<()> {
|
||||
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
||||
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
||||
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
||||
@@ -129,132 +246,382 @@ impl super::Runtime for MicroW8 {
|
||||
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
||||
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
||||
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
||||
for i in 9..64 {
|
||||
for i in 10..64 {
|
||||
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
||||
}
|
||||
let log_line = std::sync::Mutex::new(String::new());
|
||||
linker.func_wrap("env", "logChar", move |c: i32| {
|
||||
let mut log_line = log_line.lock().unwrap();
|
||||
if c == 10 {
|
||||
println!("{}", log_line);
|
||||
log_line.clear();
|
||||
} else {
|
||||
log_line.push(c as u8 as char);
|
||||
}
|
||||
})?;
|
||||
for i in 0..16 {
|
||||
linker.define(
|
||||
"env",
|
||||
&format!("g_reserved{}", i),
|
||||
wasmtime::Global::new(
|
||||
&mut store,
|
||||
let global = wasmtime::Global::new(
|
||||
&mut *store,
|
||||
GlobalType::new(ValType::I32, Mutability::Const),
|
||||
0.into(),
|
||||
)?,
|
||||
)?;
|
||||
linker.define(&store, "env", &format!("g_reserved{}", i), global)?;
|
||||
}
|
||||
|
||||
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
for export in platform_instance.exports(&mut store) {
|
||||
linker.define(
|
||||
"env",
|
||||
export.name(),
|
||||
export
|
||||
.into_func()
|
||||
fn instantiate_platform(
|
||||
linker: &mut wasmtime::Linker<()>,
|
||||
store: &mut wasmtime::Store<()>,
|
||||
platform_module: &wasmtime::Module,
|
||||
) -> Result<wasmtime::Instance> {
|
||||
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
||||
|
||||
let exports: Vec<(String, Func)> = platform_instance
|
||||
.exports(&mut *store)
|
||||
.map(|e| {
|
||||
(
|
||||
e.name().to_owned(),
|
||||
e.into_func()
|
||||
.expect("platform surely only exports functions"),
|
||||
)?;
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for (name, func) in exports {
|
||||
linker.define(&store, "env", &name, func)?;
|
||||
}
|
||||
|
||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||
interupt: store.interrupt_handle()?,
|
||||
timeout: self.timeout,
|
||||
stop: false,
|
||||
}));
|
||||
Ok(platform_instance)
|
||||
}
|
||||
|
||||
struct RegisterUpdate {
|
||||
time: i32,
|
||||
data: [u8; 32],
|
||||
}
|
||||
|
||||
struct Uw8Sound {
|
||||
stream: cpal::Stream,
|
||||
tx: mpsc::SyncSender<RegisterUpdate>,
|
||||
}
|
||||
|
||||
fn init_sound(
|
||||
engine: &wasmtime::Engine,
|
||||
platform_module: &wasmtime::Module,
|
||||
module: &wasmtime::Module,
|
||||
) -> Result<Uw8Sound> {
|
||||
let mut store = wasmtime::Store::new(engine, ());
|
||||
store.set_epoch_deadline(60);
|
||||
|
||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||
|
||||
let mut linker = wasmtime::Linker::new(engine);
|
||||
linker.define(&store, "env", "memory", memory)?;
|
||||
add_native_functions(&mut linker, &mut store)?;
|
||||
|
||||
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
||||
let instance = linker.instantiate(&mut store, module)?;
|
||||
|
||||
let snd = instance
|
||||
.get_typed_func::<(i32,), f32>(&mut store, "snd")
|
||||
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32>(&mut store, "sndGes"))?;
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.ok_or_else(|| anyhow!("No audio output device available"))?;
|
||||
let mut configs: Vec<_> = device
|
||||
.supported_output_configs()?
|
||||
.filter(|config| {
|
||||
config.sample_format() == cpal::SampleFormat::F32
|
||||
|| config.sample_format() == cpal::SampleFormat::I16
|
||||
})
|
||||
.collect();
|
||||
|
||||
if configs.is_empty() {
|
||||
eprintln!(
|
||||
"No suitable audio output config found on device \"{}\", available configs:",
|
||||
device.name()?
|
||||
);
|
||||
for config in device.supported_output_configs()? {
|
||||
eprintln!(" {}ch {}", config.channels(), config.sample_format());
|
||||
}
|
||||
bail!("Failed to configure audio out");
|
||||
}
|
||||
|
||||
configs.sort_by_key(|config| {
|
||||
let rate = 44100
|
||||
.max(config.min_sample_rate().0)
|
||||
.min(config.max_sample_rate().0);
|
||||
let rate_prio = if rate >= 44100 {
|
||||
rate - 44100
|
||||
} else {
|
||||
(44100 - rate) * 1000
|
||||
};
|
||||
let format_prio = (config.sample_format() == cpal::SampleFormat::I16) as u32;
|
||||
let channels_prio = (config.channels() != 2) as u32 * 16777216;
|
||||
rate_prio + format_prio + channels_prio
|
||||
});
|
||||
let config = configs.into_iter().next().unwrap();
|
||||
|
||||
let sample_rate = cpal::SampleRate(44100)
|
||||
.max(config.min_sample_rate())
|
||||
.min(config.max_sample_rate());
|
||||
let config = config.with_sample_rate(sample_rate);
|
||||
let buffer_size = match *config.buffer_size() {
|
||||
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
||||
cpal::SupportedBufferSize::Range { min, max } => {
|
||||
cpal::BufferSize::Fixed(256.max(min).min(max))
|
||||
}
|
||||
};
|
||||
let sample_format = config.sample_format();
|
||||
let num_channels = config.channels();
|
||||
let config = cpal::StreamConfig {
|
||||
buffer_size,
|
||||
..config.config()
|
||||
};
|
||||
|
||||
let sample_rate = config.sample_rate.0 as usize;
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel::<RegisterUpdate>(30);
|
||||
|
||||
struct Resampler {
|
||||
resampler: rubato::FftFixedIn<f32>,
|
||||
input_buffers: Vec<Vec<f32>>,
|
||||
output_buffers: Vec<Vec<f32>>,
|
||||
output_index: usize,
|
||||
}
|
||||
let mut resampler: Option<Resampler> = if sample_rate == 44100 {
|
||||
None
|
||||
} else {
|
||||
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
||||
let input_buffers = rs.input_buffer_allocate();
|
||||
let output_buffers = rs.output_buffer_allocate();
|
||||
Some(Resampler {
|
||||
resampler: rs,
|
||||
input_buffers,
|
||||
output_buffers,
|
||||
output_index: usize::MAX,
|
||||
})
|
||||
};
|
||||
|
||||
let mut sample_index = 0;
|
||||
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
|
||||
let mut current_time = 0;
|
||||
|
||||
let mut callback = move |mut outer_buffer: &mut [f32]| {
|
||||
let mut first_update = true;
|
||||
while let Ok(update) = rx.try_recv() {
|
||||
if first_update {
|
||||
current_time += update.time.wrapping_sub(current_time) / 8;
|
||||
first_update = false;
|
||||
}
|
||||
pending_updates.push(update);
|
||||
}
|
||||
|
||||
while !outer_buffer.is_empty() {
|
||||
store.set_epoch_deadline(30);
|
||||
while pending_updates
|
||||
.first()
|
||||
.into_iter()
|
||||
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
||||
{
|
||||
let update = pending_updates.remove(0);
|
||||
memory.write(&mut store, 80, &update.data).unwrap();
|
||||
}
|
||||
|
||||
let duration = if let Some(update) = pending_updates.first() {
|
||||
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
||||
} else {
|
||||
outer_buffer.len()
|
||||
};
|
||||
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
||||
|
||||
let mut buffer = &mut outer_buffer[..step_size];
|
||||
|
||||
{
|
||||
let watchdog = watchdog.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(17));
|
||||
if let Ok(mut watchdog) = watchdog.lock() {
|
||||
if watchdog.stop {
|
||||
break;
|
||||
let mem = memory.data_mut(&mut store);
|
||||
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
||||
}
|
||||
if watchdog.timeout > 0 {
|
||||
watchdog.timeout -= 1;
|
||||
if watchdog.timeout == 0 {
|
||||
watchdog.interupt.interrupt();
|
||||
|
||||
fn clamp_sample(s: f32) -> f32 {
|
||||
if s.is_nan() {
|
||||
0.0
|
||||
} else {
|
||||
s.max(-1.0).min(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut resampler) = resampler {
|
||||
while !buffer.is_empty() {
|
||||
let copy_size = resampler.output_buffers[0]
|
||||
.len()
|
||||
.saturating_sub(resampler.output_index)
|
||||
.min(buffer.len() / 2);
|
||||
if copy_size == 0 {
|
||||
resampler.input_buffers[0].clear();
|
||||
resampler.input_buffers[1].clear();
|
||||
for _ in 0..resampler.resampler.input_frames_next() {
|
||||
resampler.input_buffers[0].push(clamp_sample(
|
||||
snd.call(&mut store, (sample_index,)).unwrap_or(0.0),
|
||||
));
|
||||
resampler.input_buffers[1].push(clamp_sample(
|
||||
snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0),
|
||||
));
|
||||
sample_index = sample_index.wrapping_add(2);
|
||||
}
|
||||
|
||||
resampler
|
||||
.resampler
|
||||
.process_into_buffer(
|
||||
&resampler.input_buffers,
|
||||
&mut resampler.output_buffers,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
resampler.output_index = 0;
|
||||
} else {
|
||||
for i in 0..copy_size {
|
||||
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i];
|
||||
buffer[i * 2 + 1] =
|
||||
resampler.output_buffers[1][resampler.output_index + i];
|
||||
}
|
||||
resampler.output_index += copy_size;
|
||||
buffer = &mut buffer[copy_size * 2..];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
for v in buffer {
|
||||
*v = clamp_sample(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||
sample_index = sample_index.wrapping_add(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let instance = linker.instantiate(&mut store, &module)?;
|
||||
if let Ok(mut watchdog) = watchdog.lock() {
|
||||
watchdog.timeout = 0;
|
||||
outer_buffer = &mut outer_buffer[step_size..];
|
||||
current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
||||
}
|
||||
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
||||
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?;
|
||||
};
|
||||
|
||||
self.instance = Some(UW8Instance {
|
||||
store,
|
||||
memory,
|
||||
end_frame,
|
||||
update,
|
||||
start_time: Instant::now(),
|
||||
module: module_data.into(),
|
||||
watchdog,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_frame(&mut self) -> Result<()> {
|
||||
let mut result = Ok(());
|
||||
if let Some(mut instance) = self.instance.take() {
|
||||
fn f32_to_i16<F>(mut buffer: &mut [i16], callback: &mut F)
|
||||
where
|
||||
F: FnMut(&mut [f32]),
|
||||
{
|
||||
let time = instance.start_time.elapsed().as_millis() as i32;
|
||||
let mut gamepad: u32 = 0;
|
||||
for key in self.window.get_keys() {
|
||||
if let Some(index) = GAMEPAD_KEYS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &k)| k == key)
|
||||
.map(|(i, _)| i)
|
||||
let mut float_buffer = [0f32; 256];
|
||||
|
||||
while !buffer.is_empty() {
|
||||
let step_size = buffer.len().min(float_buffer.len());
|
||||
let step_buffer = &mut float_buffer[..step_size];
|
||||
callback(step_buffer);
|
||||
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
|
||||
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
|
||||
}
|
||||
buffer = &mut buffer[step_size..];
|
||||
}
|
||||
}
|
||||
|
||||
fn stereo_to_mono<F>(mut buffer: &mut [f32], callback: &mut F)
|
||||
where
|
||||
F: FnMut(&mut [f32]),
|
||||
{
|
||||
gamepad |= 1 << index;
|
||||
let mut in_buffer = [0f32; 256];
|
||||
|
||||
while !buffer.is_empty() {
|
||||
let step_size = buffer.len().min(in_buffer.len() / 2);
|
||||
let step_buffer = &mut in_buffer[..step_size * 2];
|
||||
callback(step_buffer);
|
||||
for (index, dest) in buffer.iter_mut().take(step_size).enumerate() {
|
||||
*dest = (step_buffer[index * 2] + step_buffer[index * 2 + 1]) * 0.5;
|
||||
}
|
||||
buffer = &mut buffer[step_size..];
|
||||
}
|
||||
}
|
||||
|
||||
let mem = instance.memory.data_mut(&mut instance.store);
|
||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
|
||||
}
|
||||
fn stereo_to_surround<F>(mut buffer: &mut [f32], num_channels: usize, callback: &mut F)
|
||||
where
|
||||
F: FnMut(&mut [f32]),
|
||||
{
|
||||
let mut in_buffer = [0f32; 256];
|
||||
buffer.fill(0.);
|
||||
|
||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
||||
watchdog.timeout = self.timeout;
|
||||
while !buffer.is_empty() {
|
||||
let step_size = (buffer.len() / num_channels).min(in_buffer.len() / 2);
|
||||
let step_buffer = &mut in_buffer[..step_size * 2];
|
||||
callback(step_buffer);
|
||||
for index in 0..step_size {
|
||||
buffer[index * num_channels + 0] = step_buffer[index * 2 + 0];
|
||||
buffer[index * num_channels + 1] = step_buffer[index * 2 + 1];
|
||||
}
|
||||
result = instance.update.call(&mut instance.store, ());
|
||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
||||
watchdog.timeout = 0;
|
||||
}
|
||||
instance.end_frame.call(&mut instance.store, ())?;
|
||||
|
||||
let memory = instance.memory.data(&instance.store);
|
||||
let framebuffer = &memory[120..(120 + 320 * 240)];
|
||||
let palette = &memory[0x13000..];
|
||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
||||
let offset = color_index as usize * 4;
|
||||
self.window_buffer[i] = 0xff000000
|
||||
| ((palette[offset] as u32) << 16)
|
||||
| ((palette[offset + 1] as u32) << 8)
|
||||
| palette[offset + 2] as u32;
|
||||
}
|
||||
|
||||
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
|
||||
self.load(&instance.module)?;
|
||||
} else if result.is_ok() {
|
||||
self.instance = Some(instance);
|
||||
buffer = &mut buffer[step_size * num_channels..];
|
||||
}
|
||||
}
|
||||
|
||||
self.window
|
||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
||||
let stream = if sample_format == cpal::SampleFormat::F32 {
|
||||
if num_channels == 2 {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [f32], _| callback(buffer),
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
} else if num_channels == 1 {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [f32], _| stereo_to_mono(buffer, &mut callback),
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
} else {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [f32], _| {
|
||||
stereo_to_surround(buffer, num_channels as usize, &mut callback)
|
||||
},
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
}
|
||||
} else {
|
||||
if num_channels == 2 {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [i16], _| f32_to_i16(buffer, &mut callback),
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
} else if num_channels == 1 {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [i16], _| {
|
||||
f32_to_i16(buffer, &mut |b| stereo_to_mono(b, &mut callback))
|
||||
},
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
} else {
|
||||
device.build_output_stream(
|
||||
&config,
|
||||
move |buffer: &mut [i16], _| {
|
||||
f32_to_i16(buffer, &mut |b| {
|
||||
stereo_to_surround(b, num_channels as usize, &mut callback)
|
||||
})
|
||||
},
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
None,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
Ok(Uw8Sound { stream, tx })
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use warp::{http::Response, Filter};
|
||||
pub struct RunWebServer {
|
||||
cart: Arc<Mutex<Vec<u8>>>,
|
||||
tx: broadcast::Sender<()>,
|
||||
socket_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl RunWebServer {
|
||||
@@ -18,8 +19,13 @@ impl RunWebServer {
|
||||
let cart = Arc::new(Mutex::new(Vec::new()));
|
||||
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_tx = tx.clone();
|
||||
let server_addr = socket_addr.clone();
|
||||
thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
@@ -47,24 +53,26 @@ impl RunWebServer {
|
||||
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
||||
});
|
||||
|
||||
let socket_addr = "127.0.0.1:3030"
|
||||
.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));
|
||||
let server_future = warp::serve(html.or(cart).or(events)).bind(server_addr);
|
||||
server_future.await
|
||||
});
|
||||
});
|
||||
|
||||
RunWebServer { cart, tx }
|
||||
RunWebServer {
|
||||
cart,
|
||||
tx,
|
||||
socket_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Runtime for RunWebServer {
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||
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.extend_from_slice(module_data);
|
||||
}
|
||||
|
||||
13
test.cwa
13
test.cwa
@@ -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)
|
||||
}
|
||||
62
test/drawing_test.cwa
Normal file
62
test/drawing_test.cwa
Normal file
@@ -0,0 +1,62 @@
|
||||
include "../examples/include/microw8-api.cwa"
|
||||
|
||||
global mut counter = 0;
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
|
||||
let col: i32 = 1;
|
||||
|
||||
loop colors {
|
||||
if !testCircle(counter, col) {
|
||||
printInt(counter);
|
||||
return;
|
||||
}
|
||||
counter += 1;
|
||||
branch_if (col +:= 1) < 256: colors;
|
||||
}
|
||||
}
|
||||
|
||||
fn testCircle(seed: i32, col: i32) -> i32 {
|
||||
randomSeed(seed);
|
||||
let cx = randomf() * 640_f - 160_f;
|
||||
let cy = randomf() * 480_f - 120_f;
|
||||
let radius = randomf() * 4_f;
|
||||
radius *= radius;
|
||||
radius *= radius;
|
||||
|
||||
circle(cx, cy, radius, col);
|
||||
|
||||
let min_x = max(0_f, floor(cx - radius - 1_f)) as i32;
|
||||
let min_y = max(0_f, floor(cy - radius - 1_f)) as i32;
|
||||
let max_x = min(320_f, ceil(cx + radius + 1_f)) as i32;
|
||||
let max_y = min(240_f, ceil(cy + radius + 1_f)) as i32;
|
||||
|
||||
let x = min_x;
|
||||
loop xloop {
|
||||
if x < max_x {
|
||||
let y = min_y;
|
||||
loop yloop {
|
||||
if y < max_y {
|
||||
let rx = x as f32 + 0.5 - cx;
|
||||
let ry = y as f32 + 0.5 - cy;
|
||||
let d = sqrt(rx*rx + ry*ry) - radius;
|
||||
if abs(d) > 0.001 {
|
||||
let is_inside = d < 0_f;
|
||||
let is_plotted = getPixel(x, y) == col;
|
||||
if is_inside != is_plotted {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
y += 1;
|
||||
branch yloop;
|
||||
}
|
||||
}
|
||||
x += 1;
|
||||
branch xloop;
|
||||
}
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
20
test/frame_time.cwa
Normal file
20
test/frame_time.cwa
Normal file
@@ -0,0 +1,20 @@
|
||||
include "../examples/include/microw8-api.cwa"
|
||||
|
||||
global mut pos = 0;
|
||||
global mut next = 0;
|
||||
|
||||
export fn upd() {
|
||||
let lazy t = 32!32;
|
||||
let lazy tick = t * 6 / 100;
|
||||
let lazy rel = t - tick * 100 / 6;
|
||||
|
||||
setBackgroundColor(select(tick == next, 0, select(tick < next, 0x35, 0x55)));
|
||||
setCursorPosition(pos % 13 * 3, pos / 13 % 30);
|
||||
if rel < 10 {
|
||||
printChar(32);
|
||||
}
|
||||
printInt(rel);
|
||||
|
||||
pos = pos + 1;
|
||||
next = tick + 1;
|
||||
}
|
||||
31
test/ges_test.cwa
Normal file
31
test/ges_test.cwa
Normal file
@@ -0,0 +1,31 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||
|
||||
include "../platform/src/ges.cwa"
|
||||
|
||||
export fn snd(t: i32) -> f32 {
|
||||
sndGes(t)
|
||||
}
|
||||
|
||||
export fn upd() {
|
||||
80?0 = 32!32 / 200 & 2 | 0x41;
|
||||
80?3 = (32!32 / 400)%8*12/7 + 40;
|
||||
let pulse = (32!32 * 256 / 2000) & 511;
|
||||
if pulse >= 256 {
|
||||
pulse = 511 - pulse;
|
||||
}
|
||||
80?1 = pulse;
|
||||
|
||||
cls(0);
|
||||
rectangle(0.0, 100.0, (pulse * 320 / 256) as f32, 16.0, 15);
|
||||
}
|
||||
|
||||
data 80 {
|
||||
i8(
|
||||
0x41, 0, 0, 80, 0x70, 0
|
||||
)
|
||||
}
|
||||
7
test/log.cwa
Normal file
7
test/log.cwa
Normal file
@@ -0,0 +1,7 @@
|
||||
include "../examples/include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
printChar('\06f: ');
|
||||
printInt(32!32 * 6 / 100);
|
||||
printChar('\n\4');
|
||||
}
|
||||
59
test/plot_ges.cwa
Normal file
59
test/plot_ges.cwa
Normal file
@@ -0,0 +1,59 @@
|
||||
include "../examples/include/microw8-api.cwa"
|
||||
|
||||
export fn upd() {
|
||||
80?0 = (32!32 >> 11 << 6) | 5;
|
||||
80?1 = (sin(time() * 6 as f32) * 95 as f32) as i32 + 128;
|
||||
plotGes();
|
||||
}
|
||||
|
||||
data 80 { i8 (
|
||||
1, 128, 0, 69, 0, 15,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0xff, 0xff,
|
||||
0xc1, 0, 0, 110, 0, 0
|
||||
) }
|
||||
|
||||
//import "env.gesSnd" fn gesSnd(i32) -> f32;
|
||||
|
||||
include "../platform/src/ges.cwa"
|
||||
|
||||
export fn snd(t: i32) -> f32 {
|
||||
gesSnd(t)
|
||||
}
|
||||
|
||||
global mut samplePos: i32 = 0;
|
||||
|
||||
const SoundBuffer = 0x30000;
|
||||
|
||||
fn plotGes() {
|
||||
rectangle(0 as f32, 10 as f32, 320 as f32, 320 as f32, 0);
|
||||
let count = (time() * 44100 as f32) as i32 * 2 - samplePos;
|
||||
let i: i32;
|
||||
loop genLoop {
|
||||
(i*4)$SoundBuffer = gesSnd(samplePos + i);
|
||||
branch_if (i := i + 1) < count: genLoop;
|
||||
}
|
||||
samplePos = samplePos + count;
|
||||
|
||||
let ch: i32;
|
||||
loop channelLoop {
|
||||
let offset = 159;
|
||||
i = 0;
|
||||
|
||||
loop searchLoop {
|
||||
offset = offset + 1;
|
||||
branch_if (offset * 8 + ch - 8)$SoundBuffer < 0 as f32 | (offset * 8 + ch)$SoundBuffer >= 0 as f32 & offset + 160 < count: searchLoop;
|
||||
}
|
||||
|
||||
offset = ch + (offset - 160) * 8;
|
||||
i = 0;
|
||||
loop plotLoop {
|
||||
setPixel(i, floor((i * 8 + offset)$SoundBuffer * 127 as f32) as i32 + 60 + ch * (120/8), 15);
|
||||
branch_if (i := i + 1) < 320: plotLoop;
|
||||
}
|
||||
|
||||
branch_if (ch := ch + 8) < 16: channelLoop;
|
||||
}
|
||||
}
|
||||
5
test/start_fn.cwa
Normal file
5
test/start_fn.cwa
Normal file
@@ -0,0 +1,5 @@
|
||||
include "../examples/include/microw8-api.cwa"
|
||||
|
||||
export fn start() {
|
||||
printChar('Test');
|
||||
}
|
||||
13
test/text_modes.cwa
Normal file
13
test/text_modes.cwa
Normal 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)
|
||||
}
|
||||
246
uw8-tool/Cargo.lock
generated
246
uw8-tool/Cargo.lock
generated
@@ -4,21 +4,27 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||
|
||||
[[package]]
|
||||
name = "cdivsufsort"
|
||||
@@ -30,32 +36,56 @@ dependencies = [
|
||||
"sacabase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
"fallible-iterator",
|
||||
"indexmap 1.9.3",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
@@ -72,10 +102,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
@@ -84,61 +128,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
name = "lexopt"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbr"
|
||||
version = "1.0.4"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2"
|
||||
checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"libc",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.4.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -153,48 +199,80 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.84"
|
||||
name = "semver"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "upkr"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cdivsufsort",
|
||||
"pbr",
|
||||
"pico-args",
|
||||
"lexopt",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -206,22 +284,24 @@ dependencies = [
|
||||
"pico-args",
|
||||
"upkr",
|
||||
"walrus",
|
||||
"wasm-encoder",
|
||||
"wasmparser 0.81.0",
|
||||
"wasm-encoder 0.201.0",
|
||||
"wasmparser 0.201.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walrus"
|
||||
version = "0.19.0"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
|
||||
checksum = "2c03529cd0c4400a2449f640d2f27cd1b48c3065226d15e26d98e4429ab0adb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gimli",
|
||||
"id-arena",
|
||||
"leb128",
|
||||
"log",
|
||||
"walrus-macro",
|
||||
"wasmparser 0.77.0",
|
||||
"wasm-encoder 0.29.0",
|
||||
"wasmparser 0.80.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -233,35 +313,43 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
name = "wasm-encoder"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.8.0"
|
||||
version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
||||
checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.77.0"
|
||||
version = "0.80.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||
checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.81.0"
|
||||
version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
||||
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap 2.2.6",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
||||
@@ -6,10 +6,10 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wasmparser = "0.81"
|
||||
wasm-encoder = "0.8"
|
||||
walrus = "0.19"
|
||||
wasmparser = "0.201"
|
||||
wasm-encoder = "0.201"
|
||||
walrus = { version = "0.20.3", default-features = false }
|
||||
anyhow = "1"
|
||||
pico-args = "0.4"
|
||||
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "2e7983fc" }
|
||||
pico-args = "0.5"
|
||||
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "080db40d0088bbee2bdf3c5c75288ac7853d6b7a" }
|
||||
pbr = "1"
|
||||
@@ -3,7 +3,7 @@ use std::{collections::HashMap, fs::File, path::Path};
|
||||
use anyhow::{bail, Result};
|
||||
use std::io::prelude::*;
|
||||
use wasm_encoder::{
|
||||
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection,
|
||||
CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, ImportSection,
|
||||
Instruction, MemoryType, Module, TypeSection, ValType,
|
||||
};
|
||||
use ValType::*;
|
||||
@@ -37,7 +37,7 @@ impl BaseModule {
|
||||
|
||||
let mut types = vec![];
|
||||
let mut type_map = HashMap::new();
|
||||
for num_params in 0..6 {
|
||||
for num_params in 0..8 {
|
||||
for num_f32 in 0..=num_params {
|
||||
for &result in &[None, Some(ValType::I32), Some(ValType::F32)] {
|
||||
let mut params = vec![];
|
||||
@@ -71,26 +71,118 @@ impl BaseModule {
|
||||
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
|
||||
|
||||
add_function(&mut functions, &type_map, "cls", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "setPixel", &[I32, I32, I32], None);
|
||||
add_function(&mut functions, &type_map, "getPixel", &[I32, I32], Some(I32));
|
||||
add_function(&mut functions, &type_map, "hline", &[I32, I32, I32, I32], None);
|
||||
add_function(&mut functions, &type_map, "rectangle", &[F32, F32, F32, F32, I32], None);
|
||||
add_function(&mut functions, &type_map, "circle", &[F32, F32, F32, I32], None);
|
||||
add_function(&mut functions, &type_map, "line", &[F32, F32, F32, F32, I32], None);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"setPixel",
|
||||
&[I32, I32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"getPixel",
|
||||
&[I32, I32],
|
||||
Some(I32),
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"hline",
|
||||
&[I32, I32, I32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"rectangle",
|
||||
&[F32, F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"circle",
|
||||
&[F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"line",
|
||||
&[F32, F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
|
||||
add_function(&mut functions, &type_map, "time", &[], Some(F32));
|
||||
add_function(&mut functions, &type_map, "isButtonPressed", &[I32], Some(I32));
|
||||
add_function(&mut functions, &type_map, "isButtonTriggered", &[I32], Some(I32));
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"isButtonPressed",
|
||||
&[I32],
|
||||
Some(I32),
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"isButtonTriggered",
|
||||
&[I32],
|
||||
Some(I32),
|
||||
);
|
||||
|
||||
add_function(&mut functions, &type_map, "printChar", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "printString", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "printInt", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "setBackgroundColor", &[I32], None);
|
||||
add_function(&mut functions, &type_map, "setCursorPosition", &[I32, I32], None);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"setBackgroundColor",
|
||||
&[I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"setCursorPosition",
|
||||
&[I32, I32],
|
||||
None,
|
||||
);
|
||||
|
||||
add_function(&mut functions, &type_map, "rectangle_outline", &[F32, F32, F32, F32, I32], None);
|
||||
add_function(&mut functions, &type_map, "circle_outline", &[F32, F32, F32, I32], None);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"rectangleOutline",
|
||||
&[F32, F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"circleOutline",
|
||||
&[F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
|
||||
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
|
||||
|
||||
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
||||
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
|
||||
|
||||
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 {
|
||||
add_function(
|
||||
@@ -141,13 +233,13 @@ impl BaseModule {
|
||||
let mut imports = ImportSection::new();
|
||||
|
||||
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 {
|
||||
imports.import(
|
||||
*module,
|
||||
Some(name.as_str()),
|
||||
name.as_str(),
|
||||
EntityType::Global(wasm_encoder::GlobalType {
|
||||
val_type: import.type_,
|
||||
mutable: import.mutable,
|
||||
@@ -157,11 +249,12 @@ impl BaseModule {
|
||||
|
||||
imports.import(
|
||||
"env",
|
||||
Some("memory"),
|
||||
"memory",
|
||||
MemoryType {
|
||||
minimum: self.memory as u64,
|
||||
maximum: None,
|
||||
memory64: false,
|
||||
shared: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -182,7 +275,7 @@ impl BaseModule {
|
||||
let mut exports = ExportSection::new();
|
||||
|
||||
for (name, fnc) in &self.exports {
|
||||
exports.export(*name, Export::Function(*fnc));
|
||||
exports.export(*name, ExportKind::Func, *fnc);
|
||||
}
|
||||
|
||||
module.section(&exports);
|
||||
@@ -210,10 +303,75 @@ impl BaseModule {
|
||||
|
||||
pub fn create_binary(path: &Path) -> Result<()> {
|
||||
let base1 = BaseModule::for_format_version(1)?.to_wasm();
|
||||
let data = upkr::pack(&base1, 4, None);
|
||||
let data = upkr::pack(&base1, 4, &upkr::Config::default(), None);
|
||||
File::create(path)?.write_all(&data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_as_cwa<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
|
||||
writeln!(
|
||||
file,
|
||||
"// MicroW8 APIs, to be `include`d in CurlyWas sources"
|
||||
)?;
|
||||
writeln!(file, "import \"env.memory\" memory({});", base.memory)?;
|
||||
writeln!(file)?;
|
||||
for &(module, ref name, type_id) in &base.function_imports {
|
||||
if !name.contains("reserved") {
|
||||
let ty = &base.types[type_id as usize];
|
||||
let params: Vec<&str> = ty.params.iter().copied().map(type_to_str).collect();
|
||||
write!(
|
||||
file,
|
||||
"import \"{}.{}\" fn {}({})",
|
||||
module,
|
||||
name,
|
||||
name,
|
||||
params.join(", ")
|
||||
)?;
|
||||
if let Some(result) = ty.result {
|
||||
write!(file, " -> {}", type_to_str(result))?;
|
||||
}
|
||||
writeln!(file, ";")?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(file)?;
|
||||
for &(name, value) in CONSTANTS {
|
||||
writeln!(file, "const {} = 0x{:x};", name, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
inner(File::create(path)?, self)
|
||||
}
|
||||
|
||||
pub fn write_as_wat<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
|
||||
writeln!(file, ";; MicroW8 APIs, in WAT (Wasm Text) format")?;
|
||||
writeln!(file, "(import \"env\" \"memory\" (memory {}))", base.memory)?;
|
||||
writeln!(file)?;
|
||||
for &(module, ref name, type_id) in &base.function_imports {
|
||||
if !name.contains("reserved") {
|
||||
let ty = &base.types[type_id as usize];
|
||||
write!(file, "(import \"{}\" \"{}\" (func ${}", module, name, name)?;
|
||||
for ¶m in &ty.params {
|
||||
write!(file, " (param {})", type_to_str(param))?;
|
||||
}
|
||||
if let Some(result) = ty.result {
|
||||
write!(file, " (result {})", type_to_str(result))?;
|
||||
}
|
||||
writeln!(file, "))")?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(file)?;
|
||||
writeln!(file, ";; to use defines, include this file with a preprocessor\n;; like gpp (https://logological.org/gpp).")?;
|
||||
for &(name, value) in CONSTANTS {
|
||||
writeln!(file, "#define {} 0x{:x};", name, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
inner(File::create(path)?, self)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_function(
|
||||
@@ -241,3 +399,30 @@ fn lookup_type(
|
||||
};
|
||||
*type_map.get(&key).unwrap()
|
||||
}
|
||||
|
||||
fn type_to_str(ty: ValType) -> &'static str {
|
||||
match ty {
|
||||
ValType::I32 => "i32",
|
||||
ValType::I64 => "i64",
|
||||
ValType::F32 => "f32",
|
||||
ValType::F64 => "f64",
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
const CONSTANTS: &[(&str, u32)] = &[
|
||||
("TIME_MS", 0x40),
|
||||
("GAMEPAD", 0x44),
|
||||
("FRAMEBUFFER", 0x78),
|
||||
("PALETTE", 0x13000),
|
||||
("FONT", 0x13400),
|
||||
("USER_MEM", 0x14000),
|
||||
("BUTTON_UP", 0),
|
||||
("BUTTON_DOWN", 1),
|
||||
("BUTTON_LEFT", 2),
|
||||
("BUTTON_RIGHT", 3),
|
||||
("BUTTON_A", 4),
|
||||
("BUTTON_B", 5),
|
||||
("BUTTON_X", 6),
|
||||
("BUTTON_Y", 7),
|
||||
];
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
||||
let mut module = walrus::Module::from_file(in_path)?;
|
||||
|
||||
let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() {
|
||||
"upd" => None,
|
||||
_ => Some(export.id())
|
||||
}).collect();
|
||||
let exports_to_delete: Vec<_> = module
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|export| match export.name.as_str() {
|
||||
"start" | "upd" | "snd" => None,
|
||||
_ => Some(export.id()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
for id in exports_to_delete {
|
||||
module.exports.delete(id);
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
mod base_module;
|
||||
mod pack;
|
||||
mod filter_exports;
|
||||
mod pack;
|
||||
|
||||
pub use base_module::BaseModule;
|
||||
pub use pack::{pack, pack_file, unpack, unpack_file, PackConfig};
|
||||
pub use filter_exports::filter_exports;
|
||||
pub use pack::{pack, pack_file, unpack, unpack_file, PackConfig};
|
||||
|
||||
pub fn compressed_size(cart: &[u8]) -> f32 {
|
||||
if cart[0] != 2 {
|
||||
cart.len() as f32
|
||||
} else {
|
||||
upkr::compressed_size(&cart[1..]) + 1.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use uw8_tool::BaseModule;
|
||||
use pico_args::Arguments;
|
||||
use uw8_tool::BaseModule;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = Arguments::from_env();
|
||||
@@ -32,6 +32,14 @@ fn main() -> Result<()> {
|
||||
let dest: PathBuf = args.free_from_str()?;
|
||||
uw8_tool::filter_exports(&source, &dest)?;
|
||||
}
|
||||
"base-cwa" => {
|
||||
let path: PathBuf = args.free_from_str()?;
|
||||
BaseModule::for_format_version(1)?.write_as_cwa(path)?;
|
||||
}
|
||||
"base-wat" => {
|
||||
let path: PathBuf = args.free_from_str()?;
|
||||
BaseModule::for_format_version(1)?.write_as_wat(path)?;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown subcommand '{}'", cmd);
|
||||
print_help();
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
use wasm_encoder as enc;
|
||||
use wasmparser::{
|
||||
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
||||
ImportSectionEntryType, ImportSectionReader, TableSectionReader, TypeSectionReader,
|
||||
ImportSectionReader, TableSectionReader, TypeRef, TypeSectionReader,
|
||||
};
|
||||
|
||||
pub struct PackConfig {
|
||||
@@ -63,6 +63,7 @@ pub fn pack(data: &[u8], config: &PackConfig) -> Result<Vec<u8>> {
|
||||
uw8.extend_from_slice(&upkr::pack(
|
||||
&result[8..],
|
||||
level,
|
||||
&upkr::Config::default(),
|
||||
Some(&mut |pos| {
|
||||
pb.set(pos as u64);
|
||||
}),
|
||||
@@ -89,7 +90,10 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
|
||||
let (version, data) = match data[0] {
|
||||
0 => return Ok(data),
|
||||
1 => (1, data[1..].to_vec()),
|
||||
2 => (1, upkr::unpack(&data[1..])),
|
||||
2 => (
|
||||
1,
|
||||
upkr::unpack(&data[1..], &upkr::Config::default(), 4 * 1024 * 1024)?,
|
||||
),
|
||||
other => bail!("Uknown format version {}", other),
|
||||
};
|
||||
|
||||
@@ -132,8 +136,8 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
fn to_val_type(type_: &wasmparser::Type) -> Result<ValType> {
|
||||
use wasmparser::Type::*;
|
||||
fn to_val_type(type_: &wasmparser::ValType) -> Result<ValType> {
|
||||
use wasmparser::ValType::*;
|
||||
Ok(match *type_ {
|
||||
I32 => ValType::I32,
|
||||
I64 => ValType::I64,
|
||||
@@ -143,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()
|
||||
}
|
||||
|
||||
@@ -201,7 +205,7 @@ impl<'a> ParsedModule<'a> {
|
||||
import_section = Some(Section::new(range, ImportSection::parse(reader)?));
|
||||
}
|
||||
Payload::GlobalSection(reader) => {
|
||||
global_section = Some(Section::new(range, reader.get_count()));
|
||||
global_section = Some(Section::new(range, reader.count()));
|
||||
}
|
||||
Payload::FunctionSection(reader) => {
|
||||
function_section = Some(Section::new(range, read_function_section(reader)?));
|
||||
@@ -219,17 +223,22 @@ impl<'a> ParsedModule<'a> {
|
||||
validate_table_section(reader)?;
|
||||
table_section = Some(Section::new(range, ()));
|
||||
}
|
||||
Payload::ElementSection(mut reader) => {
|
||||
let mut elements = Vec::with_capacity(reader.get_count() as usize);
|
||||
for _ in 0..reader.get_count() {
|
||||
elements.push(Element::parse(reader.read()?)?);
|
||||
Payload::MemorySection(reader) => {
|
||||
if reader.count() != 0 {
|
||||
bail!("Found non-empty MemorySection. Memory has to be imported!");
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
Payload::CodeSectionStart { .. } => (),
|
||||
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
||||
Payload::CustomSection { .. } => (),
|
||||
Payload::End => break,
|
||||
Payload::End(..) => break,
|
||||
other => bail!("Unsupported section: {:?}", other),
|
||||
}
|
||||
|
||||
@@ -457,7 +466,7 @@ impl<'a> ParsedModule<'a> {
|
||||
{
|
||||
let mut export_section = enc::ExportSection::new();
|
||||
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);
|
||||
}
|
||||
@@ -480,8 +489,7 @@ impl<'a> ParsedModule<'a> {
|
||||
}
|
||||
element_section.active(
|
||||
None,
|
||||
&wasm_encoder::Instruction::I32Const(element.start_index as i32),
|
||||
ValType::FuncRef,
|
||||
&wasm_encoder::ConstExpr::i32_const(element.start_index as i32),
|
||||
wasm_encoder::Elements::Functions(&functions),
|
||||
);
|
||||
}
|
||||
@@ -527,30 +535,32 @@ fn copy_section(module: &mut wasm_encoder::Module, data: &[u8]) -> Result<()> {
|
||||
fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::FunctionType>> {
|
||||
let mut function_types = vec![];
|
||||
|
||||
for type_def in reader {
|
||||
match type_def? {
|
||||
wasmparser::TypeDef::Func(fnc) => {
|
||||
if fnc.returns.len() > 1 {
|
||||
for rec_group in reader {
|
||||
for sub_type in rec_group?.into_types() {
|
||||
match sub_type.composite_type {
|
||||
wasmparser::CompositeType::Func(fnc) => {
|
||||
if fnc.results().len() > 1 {
|
||||
bail!("Multi-value not supported");
|
||||
}
|
||||
let params = to_val_type_vec(&fnc.params)?;
|
||||
let result = to_val_type_vec(&fnc.returns)?.into_iter().next();
|
||||
let params = to_val_type_vec(fnc.params())?;
|
||||
let result = to_val_type_vec(fnc.results())?.into_iter().next();
|
||||
function_types.push(FunctionType { params, result });
|
||||
}
|
||||
t => bail!("Unsupported type def {:?}", t),
|
||||
_ => bail!("Only function types are supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(function_types)
|
||||
}
|
||||
|
||||
fn validate_table_section(mut reader: TableSectionReader) -> Result<()> {
|
||||
if reader.get_count() != 1 {
|
||||
fn validate_table_section(reader: TableSectionReader) -> Result<()> {
|
||||
if reader.count() != 1 {
|
||||
bail!("Only up to one table supported");
|
||||
}
|
||||
|
||||
let type_ = reader.read()?;
|
||||
if type_.element_type != wasmparser::Type::FuncRef {
|
||||
let table = reader.into_iter().next().unwrap()?;
|
||||
if !table.ty.element_type.is_func_ref() {
|
||||
bail!("Only one funcref table is supported");
|
||||
}
|
||||
|
||||
@@ -584,21 +594,20 @@ impl ImportSection {
|
||||
|
||||
for import in reader {
|
||||
let import = import?;
|
||||
if let Some(field) = import.field {
|
||||
match import.ty {
|
||||
ImportSectionEntryType::Function(type_) => {
|
||||
TypeRef::Func(type_) => {
|
||||
functions.push(FunctionImport {
|
||||
module: import.module.to_string(),
|
||||
field: field.to_string(),
|
||||
field: import.name.to_string(),
|
||||
type_,
|
||||
});
|
||||
}
|
||||
ImportSectionEntryType::Memory(mem) => {
|
||||
if import.module != "env" || field != "memory" {
|
||||
TypeRef::Memory(mem) => {
|
||||
if import.module != "env" || import.name != "memory" {
|
||||
bail!(
|
||||
"Wrong name of memory import {}.{}, should be env.memory",
|
||||
import.module,
|
||||
field
|
||||
import.name
|
||||
);
|
||||
}
|
||||
if mem.memory64 || mem.shared {
|
||||
@@ -606,10 +615,10 @@ impl ImportSection {
|
||||
}
|
||||
memory = mem.maximum.unwrap_or(mem.initial) as u32;
|
||||
}
|
||||
ImportSectionEntryType::Global(glbl) => {
|
||||
TypeRef::Global(glbl) => {
|
||||
globals.push(GlobalImport {
|
||||
module: import.module.to_string(),
|
||||
field: field.to_string(),
|
||||
field: import.name.to_string(),
|
||||
type_: GlobalType {
|
||||
type_: to_val_type(&glbl.content_type)?,
|
||||
mutable: glbl.mutable,
|
||||
@@ -618,12 +627,6 @@ impl ImportSection {
|
||||
}
|
||||
_ => bail!("Unsupported import item {:?}", import.ty),
|
||||
}
|
||||
} else {
|
||||
bail!(
|
||||
"Found import without field, only module '{}'",
|
||||
import.module
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImportSection {
|
||||
@@ -642,17 +645,20 @@ struct Element {
|
||||
|
||||
impl Element {
|
||||
fn parse(element: wasmparser::Element) -> Result<Element> {
|
||||
if element.ty != wasmparser::Type::FuncRef {
|
||||
bail!("Table element type is not FuncRef");
|
||||
}
|
||||
|
||||
match element.items {
|
||||
wasmparser::ElementItems::Functions(funcs_reader) => {
|
||||
let start_index = if let wasmparser::ElementKind::Active {
|
||||
init_expr,
|
||||
table_index: 0,
|
||||
offset_expr,
|
||||
table_index,
|
||||
} = element.kind
|
||||
{
|
||||
let mut init_reader = init_expr.get_operators_reader();
|
||||
if let wasmparser::Operator::I32Const { value: start_index } = init_reader.read()? {
|
||||
if table_index.map(|i| i > 0).unwrap_or(false) {
|
||||
bail!("Only function table with index 0 is supported");
|
||||
}
|
||||
let mut init_reader = offset_expr.get_operators_reader();
|
||||
if let wasmparser::Operator::I32Const { value: start_index } =
|
||||
init_reader.read()?
|
||||
{
|
||||
start_index as u32
|
||||
} else {
|
||||
bail!("Table element start index is not a integer constant");
|
||||
@@ -661,15 +667,9 @@ impl Element {
|
||||
bail!("Unsupported table element kind");
|
||||
};
|
||||
|
||||
let mut items_reader = element.items.get_items_reader()?;
|
||||
|
||||
let mut functions = Vec::with_capacity(items_reader.get_count() as usize);
|
||||
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");
|
||||
}
|
||||
let mut functions = Vec::with_capacity(funcs_reader.count() as usize);
|
||||
for index in funcs_reader {
|
||||
functions.push(index?);
|
||||
}
|
||||
|
||||
Ok(Element {
|
||||
@@ -677,6 +677,9 @@ impl Element {
|
||||
functions,
|
||||
})
|
||||
}
|
||||
_ => bail!("Table element type is not FuncRef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -706,8 +709,8 @@ fn read_export_section(reader: ExportSectionReader) -> Result<Vec<(String, u32)>
|
||||
for export in reader {
|
||||
let export = export?;
|
||||
match export.kind {
|
||||
ExternalKind::Function => {
|
||||
function_exports.push((export.field.to_string(), export.index));
|
||||
ExternalKind::Func => {
|
||||
function_exports.push((export.name.to_string(), export.index));
|
||||
}
|
||||
_ => (), // just ignore all other kinds since MicroW8 doesn't expect any exports other than functions
|
||||
}
|
||||
@@ -728,13 +731,11 @@ fn remap_function(
|
||||
}
|
||||
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 {
|
||||
wasmparser::TypeOrFuncType::Type(wasmparser::Type::EmptyBlockType) => {
|
||||
enc::BlockType::Empty
|
||||
}
|
||||
wasmparser::TypeOrFuncType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
|
||||
wasmparser::TypeOrFuncType::FuncType(ty) => enc::BlockType::FunctionType(
|
||||
wasmparser::BlockType::Empty => enc::BlockType::Empty,
|
||||
wasmparser::BlockType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
|
||||
wasmparser::BlockType::FuncType(ty) => enc::BlockType::FunctionType(
|
||||
*type_map
|
||||
.get(&ty)
|
||||
.ok_or_else(|| anyhow!("Function type index out of range: {}", ty))?,
|
||||
@@ -748,7 +749,7 @@ fn remap_function(
|
||||
.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 {
|
||||
offset: m.offset,
|
||||
align: m.align as u32,
|
||||
@@ -763,24 +764,31 @@ fn remap_function(
|
||||
function.instruction(&match op? {
|
||||
De::Unreachable => En::Unreachable,
|
||||
De::Nop => En::Nop,
|
||||
De::Block { ty } => En::Block(block_type(ty)?),
|
||||
De::Loop { ty } => En::Loop(block_type(ty)?),
|
||||
De::If { ty } => En::If(block_type(ty)?),
|
||||
De::Block { blockty } => En::Block(block_type(blockty)?),
|
||||
De::Loop { blockty } => En::Loop(block_type(blockty)?),
|
||||
De::If { blockty } => En::If(block_type(blockty)?),
|
||||
De::Else => En::Else,
|
||||
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
|
||||
De::End => En::End,
|
||||
De::Br { relative_depth } => En::Br(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::Call { function_index } => En::Call(
|
||||
*function_map
|
||||
.get(&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
|
||||
.get(&index)
|
||||
.get(&type_index)
|
||||
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
|
||||
table: table_index,
|
||||
},
|
||||
@@ -800,16 +808,16 @@ fn remap_function(
|
||||
De::I64Load { memarg } => En::I64Load(mem(memarg)),
|
||||
De::F32Load { memarg } => En::F32Load(mem(memarg)),
|
||||
De::F64Load { memarg } => En::F64Load(mem(memarg)),
|
||||
De::I32Load8S { memarg } => En::I32Load8_S(mem(memarg)),
|
||||
De::I32Load8U { memarg } => En::I32Load8_U(mem(memarg)),
|
||||
De::I32Load16S { memarg } => En::I32Load16_S(mem(memarg)),
|
||||
De::I32Load16U { memarg } => En::I32Load16_U(mem(memarg)),
|
||||
De::I64Load8S { memarg } => En::I64Load8_S(mem(memarg)),
|
||||
De::I64Load8U { memarg } => En::I64Load8_U(mem(memarg)),
|
||||
De::I64Load16S { memarg } => En::I64Load16_S(mem(memarg)),
|
||||
De::I64Load16U { memarg } => En::I64Load16_U(mem(memarg)),
|
||||
De::I64Load32S { memarg } => En::I64Load32_S(mem(memarg)),
|
||||
De::I64Load32U { memarg } => En::I64Load32_U(mem(memarg)),
|
||||
De::I32Load8S { memarg } => En::I32Load8S(mem(memarg)),
|
||||
De::I32Load8U { memarg } => En::I32Load8U(mem(memarg)),
|
||||
De::I32Load16S { memarg } => En::I32Load16S(mem(memarg)),
|
||||
De::I32Load16U { memarg } => En::I32Load16U(mem(memarg)),
|
||||
De::I64Load8S { memarg } => En::I64Load8S(mem(memarg)),
|
||||
De::I64Load8U { memarg } => En::I64Load8U(mem(memarg)),
|
||||
De::I64Load16S { memarg } => En::I64Load16S(mem(memarg)),
|
||||
De::I64Load16U { memarg } => En::I64Load16U(mem(memarg)),
|
||||
De::I64Load32S { memarg } => En::I64Load32S(mem(memarg)),
|
||||
De::I64Load32U { memarg } => En::I64Load32U(mem(memarg)),
|
||||
De::I32Store { memarg } => En::I32Store(mem(memarg)),
|
||||
De::I64Store { memarg } => En::I64Store(mem(memarg)),
|
||||
De::F32Store { memarg } => En::F32Store(mem(memarg)),
|
||||
@@ -828,7 +836,7 @@ fn remap_function(
|
||||
De::RefNull { .. } | De::RefIsNull { .. } | De::RefFunc { .. } => todo!(),
|
||||
De::I32Eqz => En::I32Eqz,
|
||||
De::I32Eq => En::I32Eq,
|
||||
De::I32Ne => En::I32Neq,
|
||||
De::I32Ne => En::I32Ne,
|
||||
De::I32LtS => En::I32LtS,
|
||||
De::I32LtU => En::I32LtU,
|
||||
De::I32GtS => En::I32GtS,
|
||||
@@ -839,7 +847,7 @@ fn remap_function(
|
||||
De::I32GeU => En::I32GeU,
|
||||
De::I64Eqz => En::I64Eqz,
|
||||
De::I64Eq => En::I64Eq,
|
||||
De::I64Ne => En::I64Neq,
|
||||
De::I64Ne => En::I64Ne,
|
||||
De::I64LtS => En::I64LtS,
|
||||
De::I64LtU => En::I64LtU,
|
||||
De::I64GtS => En::I64GtS,
|
||||
@@ -849,13 +857,13 @@ fn remap_function(
|
||||
De::I64GeS => En::I64GeS,
|
||||
De::I64GeU => En::I64GeU,
|
||||
De::F32Eq => En::F32Eq,
|
||||
De::F32Ne => En::F32Neq,
|
||||
De::F32Ne => En::F32Ne,
|
||||
De::F32Lt => En::F32Lt,
|
||||
De::F32Gt => En::F32Gt,
|
||||
De::F32Le => En::F32Le,
|
||||
De::F32Ge => En::F32Ge,
|
||||
De::F64Eq => En::F64Eq,
|
||||
De::F64Ne => En::F64Neq,
|
||||
De::F64Ne => En::F64Ne,
|
||||
De::F64Lt => En::F64Lt,
|
||||
De::F64Gt => En::F64Gt,
|
||||
De::F64Le => En::F64Le,
|
||||
@@ -962,6 +970,8 @@ fn remap_function(
|
||||
De::I64TruncSatF32U => En::I64TruncSatF32U,
|
||||
De::I64TruncSatF64S => En::I64TruncSatF64S,
|
||||
De::I64TruncSatF64U => En::I64TruncSatF64U,
|
||||
De::MemoryCopy { src_mem, dst_mem } => En::MemoryCopy { src_mem, dst_mem },
|
||||
De::MemoryFill { mem } => En::MemoryFill(mem),
|
||||
other => bail!("Unsupported instruction {:?}", other),
|
||||
});
|
||||
}
|
||||
|
||||
1
uw8-window/.gitignore
vendored
Normal file
1
uw8-window/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
2228
uw8-window/Cargo.lock
generated
Normal file
2228
uw8-window/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
uw8-window/Cargo.toml
Normal file
18
uw8-window/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "uw8-window"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.11.3"
|
||||
winit = "0.28.6"
|
||||
log = "0.4"
|
||||
pico-args = "0.5"
|
||||
wgpu = "0.17"
|
||||
pollster = "0.3.0"
|
||||
bytemuck = { version = "1.15", features = [ "derive" ] }
|
||||
anyhow = "1"
|
||||
minifb = { version = "0.25.0", default-features = false, features = ["x11"] }
|
||||
winapi = { version = "0.3.9", features = [ "timeapi" ] }
|
||||
83
uw8-window/src/cpu.rs
Normal file
83
uw8-window/src/cpu.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::{Input, WindowImpl};
|
||||
use anyhow::Result;
|
||||
use minifb::{Key, WindowOptions};
|
||||
|
||||
static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::Up,
|
||||
Key::Down,
|
||||
Key::Left,
|
||||
Key::Right,
|
||||
Key::Z,
|
||||
Key::X,
|
||||
Key::A,
|
||||
Key::S,
|
||||
];
|
||||
|
||||
pub struct Window {
|
||||
window: minifb::Window,
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new() -> Result<Window> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
winapi::um::timeapi::timeBeginPeriod(1);
|
||||
}
|
||||
|
||||
let buffer: Vec<u32> = vec![0; 320 * 240];
|
||||
|
||||
let options = WindowOptions {
|
||||
scale: minifb::Scale::X2,
|
||||
scale_mode: minifb::ScaleMode::AspectRatioStretch,
|
||||
resize: true,
|
||||
..Default::default()
|
||||
};
|
||||
let window = minifb::Window::new("MicroW8", 320, 240, options).unwrap();
|
||||
|
||||
Ok(Window { window, buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowImpl for Window {
|
||||
fn begin_frame(&mut self) -> Input {
|
||||
let mut gamepads = [0u8; 4];
|
||||
for key in self.window.get_keys() {
|
||||
if let Some(index) = GAMEPAD_KEYS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &k)| k == key)
|
||||
.map(|(i, _)| i)
|
||||
{
|
||||
gamepads[0] |= 1 << index;
|
||||
}
|
||||
}
|
||||
|
||||
Input {
|
||||
gamepads,
|
||||
reset: self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
|
||||
}
|
||||
}
|
||||
|
||||
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
||||
let offset = color_index as usize * 4;
|
||||
self.buffer[i] = 0xff000000
|
||||
| ((palette[offset] as u32) << 16)
|
||||
| ((palette[offset + 1] as u32) << 8)
|
||||
| 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)
|
||||
}
|
||||
}
|
||||
143
uw8-window/src/gpu/crt.rs
Normal file
143
uw8-window/src/gpu/crt.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use super::Filter;
|
||||
|
||||
pub struct CrtFilter {
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl CrtFilter {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
screen: &wgpu::TextureView,
|
||||
resolution: PhysicalSize<u32>,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
) -> CrtFilter {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(resolution),
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let crt_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let crt_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &crt_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&screen),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let crt_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("crt.wgsl").into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&crt_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &crt_shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &crt_shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: Default::default(),
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
CrtFilter {
|
||||
uniform_buffer,
|
||||
bind_group: crt_bind_group,
|
||||
pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for CrtFilter {
|
||||
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(new_size),
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_scale_from_resolution(res: PhysicalSize<u32>) -> [f32; 4] {
|
||||
let scale = ((res.width as f32) / 160.0).min((res.height as f32) / 120.0);
|
||||
[
|
||||
res.width as f32 / scale,
|
||||
res.height as f32 / scale,
|
||||
2.0 / scale,
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
texture_scale: [f32; 4],
|
||||
}
|
||||
75
uw8-window/src/gpu/crt.wgsl
Normal file
75
uw8-window/src/gpu/crt.wgsl
Normal file
@@ -0,0 +1,75 @@
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct Uniforms {
|
||||
texture_scale: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(0) @binding(1) var<uniform> uniforms: Uniforms;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) in_vertex_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let i = in_vertex_index / 3u + in_vertex_index % 3u;
|
||||
let x = -1.0 + f32(i % 2u) * 322.0;
|
||||
let y = -1.0 + f32(i / 2u) * 242.0;
|
||||
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) / uniforms.texture_scale.xy, 0.0, 1.0);
|
||||
out.tex_coords = vec2<f32>(x, y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
|
||||
|
||||
fn sample_pixel(coords: vec2<i32>, offset: vec4<f32>) -> vec3<f32> {
|
||||
let is_outside = any(vec2<u32>(coords) >= vec2<u32>(320u, 240u));
|
||||
if(is_outside) {
|
||||
return vec3<f32>(0.0);
|
||||
} else {
|
||||
let f = max(vec4<f32>(0.008) / offset - vec4<f32>(0.0024), vec4<f32>(0.0));
|
||||
return textureLoad(screen_texture, coords, 0).rgb * (f.x + f.y + f.z + f.w);
|
||||
}
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let pixelf = floor(in.tex_coords);
|
||||
let o = vec2<f32>(0.5) - (in.tex_coords - pixelf);
|
||||
let pixel = vec2<i32>(pixelf);
|
||||
|
||||
let offset_x = o.xxxx + vec4<f32>(-0.125, 0.375, 0.125, -0.375) * uniforms.texture_scale.z;
|
||||
let offset_y = o.yyyy + vec4<f32>(-0.375, -0.125, 0.375, 0.125) * uniforms.texture_scale.z;
|
||||
|
||||
var offset_x0 = max(abs(offset_x + vec4<f32>(-1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
|
||||
var offset_x1 = max(abs(offset_x) - vec4<f32>(0.5), vec4<f32>(0.0));
|
||||
var offset_x2 = max(abs(offset_x + vec4<f32>(1.0)) - vec4<f32>(0.5), vec4<f32>(0.0));
|
||||
|
||||
offset_x0 = offset_x0 * offset_x0;
|
||||
offset_x1 = offset_x1 * offset_x1;
|
||||
offset_x2 = offset_x2 * offset_x2;
|
||||
|
||||
var offset_yr = offset_y + vec4<f32>(-1.0);
|
||||
offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
|
||||
|
||||
var acc = sample_pixel(pixel + vec2<i32>(-1, -1), offset_x0 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(0, -1), offset_x1 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(1, -1), offset_x2 + offset_yr);
|
||||
|
||||
offset_yr = vec4<f32>(0.02) + offset_y * offset_y;
|
||||
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(-1, 0), offset_x0 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel, offset_x1 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(1, 0), offset_x2 + offset_yr);
|
||||
|
||||
offset_yr = offset_y + vec4<f32>(1.0);
|
||||
offset_yr = vec4<f32>(0.02) + offset_yr * offset_yr;
|
||||
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(-1, 1), offset_x0 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(0, 1), offset_x1 + offset_yr);
|
||||
acc = acc + sample_pixel(pixel + vec2<i32>(1, 1), offset_x2 + offset_yr);
|
||||
|
||||
return vec4<f32>(acc, 1.0);
|
||||
}
|
||||
162
uw8-window/src/gpu/fast_crt.rs
Normal file
162
uw8-window/src/gpu/fast_crt.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use super::Filter;
|
||||
|
||||
pub struct FastCrtFilter {
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl FastCrtFilter {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
screen: &wgpu::TextureView,
|
||||
resolution: PhysicalSize<u32>,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
chromatic: bool,
|
||||
) -> FastCrtFilter {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(resolution),
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&screen),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("fast_crt.wgsl").into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: if chromatic {
|
||||
"fs_main_chromatic"
|
||||
} else {
|
||||
"fs_main"
|
||||
},
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: Default::default(),
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
FastCrtFilter {
|
||||
uniform_buffer,
|
||||
bind_group,
|
||||
pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for FastCrtFilter {
|
||||
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(new_size),
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_scale_from_resolution(res: PhysicalSize<u32>) -> [f32; 4] {
|
||||
let scale = ((res.width as f32) / 160.0).min((res.height as f32) / 120.0);
|
||||
[
|
||||
scale / res.width as f32,
|
||||
scale / res.height as f32,
|
||||
2.0 / scale,
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
texture_scale: [f32; 4],
|
||||
}
|
||||
66
uw8-window/src/gpu/fast_crt.wgsl
Normal file
66
uw8-window/src/gpu/fast_crt.wgsl
Normal file
@@ -0,0 +1,66 @@
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct Uniforms {
|
||||
texture_scale: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) in_vertex_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let i = in_vertex_index / 3u + in_vertex_index % 3u;
|
||||
let x = 0.0 + f32(i % 2u) * 320.0;
|
||||
let y = 0.0 + f32(i / 2u) * 240.0;
|
||||
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0);
|
||||
out.tex_coords = vec2<f32>(x, y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var linear_sampler: sampler;
|
||||
|
||||
fn row_factor(offset: f32) -> f32 {
|
||||
return 1.0 / (1.0 + offset * offset * 16.0);
|
||||
}
|
||||
|
||||
fn col_factor(offset: f32) -> f32 {
|
||||
let o = max(0.0, abs(offset) - 0.4);
|
||||
return 1.0 / (1.0 + o * o * 16.0);
|
||||
}
|
||||
|
||||
fn sample_screen(tex_coords: vec2<f32>) -> vec4<f32> {
|
||||
let base = round(tex_coords) - vec2<f32>(0.5);
|
||||
let frac = tex_coords - base;
|
||||
|
||||
let top_factor = row_factor(frac.y);
|
||||
let bottom_factor = row_factor(frac.y - 1.0);
|
||||
|
||||
let v = base.y + bottom_factor / (bottom_factor + top_factor);
|
||||
|
||||
let left_factor = col_factor(frac.x);
|
||||
let right_factor = col_factor(frac.x - 1.0);
|
||||
|
||||
let u = base.x + right_factor / (right_factor + left_factor);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
554
uw8-window/src/gpu/mod.rs
Normal file
554
uw8-window/src/gpu/mod.rs
Normal file
@@ -0,0 +1,554 @@
|
||||
use crate::{Input, WindowConfig, WindowImpl};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::time::Instant;
|
||||
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
use winit::platform::run_return::EventLoopExtRunReturn;
|
||||
|
||||
mod crt;
|
||||
mod fast_crt;
|
||||
mod square;
|
||||
|
||||
use crt::CrtFilter;
|
||||
use fast_crt::FastCrtFilter;
|
||||
use square::SquareFilter;
|
||||
|
||||
pub struct Window {
|
||||
_instance: wgpu::Instance,
|
||||
surface: wgpu::Surface,
|
||||
_adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
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 {
|
||||
pub fn new(window_config: WindowConfig) -> Result<Window> {
|
||||
async fn create(window_config: WindowConfig) -> Result<Window> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(
|
||||
(320. * window_config.scale).round() as u32,
|
||||
(240. * window_config.scale).round() as u32,
|
||||
))
|
||||
.with_min_inner_size(PhysicalSize::new(320u32, 240))
|
||||
.with_title("MicroW8")
|
||||
.with_fullscreen(if window_config.fullscreen {
|
||||
Some(Fullscreen::Borderless(None))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.build(&event_loop)?;
|
||||
|
||||
window.set_cursor_visible(false);
|
||||
|
||||
let instance = wgpu::Instance::new(Default::default());
|
||||
let surface = unsafe { instance.create_surface(&window) }?;
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("Request adapter failed"))?;
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor::default(), None)
|
||||
.await?;
|
||||
|
||||
let palette_screen_mode = PaletteScreenMode::new(&device);
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
present_mode: wgpu::PresentMode::AutoNoVsync,
|
||||
..surface
|
||||
.get_default_config(
|
||||
&adapter,
|
||||
window.inner_size().width,
|
||||
window.inner_size().height,
|
||||
)
|
||||
.expect("Surface incompatible with adapter")
|
||||
};
|
||||
|
||||
let filter: Box<dyn Filter> = create_filter(
|
||||
&device,
|
||||
&palette_screen_mode.screen_view,
|
||||
window.inner_size(),
|
||||
surface_config.format,
|
||||
window_config.filter,
|
||||
);
|
||||
|
||||
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;
|
||||
self.event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::WaitUntil(self.next_frame);
|
||||
let mut new_filter = None;
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::Resized(new_size) => {
|
||||
self.surface_config.width = new_size.width;
|
||||
self.surface_config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.surface_config);
|
||||
self.filter.resize(&self.queue, new_size);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
self.is_open = false;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
|
||||
match input.scancode {
|
||||
44 => 16,
|
||||
45 => 32,
|
||||
30 => 64,
|
||||
31 => 128,
|
||||
_ => match input.virtual_keycode {
|
||||
Some(VirtualKeyCode::Up) => 1,
|
||||
Some(VirtualKeyCode::Down) => 2,
|
||||
Some(VirtualKeyCode::Left) => 4,
|
||||
Some(VirtualKeyCode::Right) => 8,
|
||||
_ => 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
if input.state == winit::event::ElementState::Pressed {
|
||||
match input.virtual_keycode {
|
||||
Some(VirtualKeyCode::Escape) => {
|
||||
self.is_open = false;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Some(VirtualKeyCode::F) => {
|
||||
let fullscreen = if self.window.fullscreen().is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(Fullscreen::Borderless(None))
|
||||
};
|
||||
self.is_fullscreen = fullscreen.is_some();
|
||||
self.window.set_fullscreen(fullscreen);
|
||||
}
|
||||
Some(VirtualKeyCode::R) => reset = true,
|
||||
Some(VirtualKeyCode::Key1) => new_filter = Some(1),
|
||||
Some(VirtualKeyCode::Key2) => new_filter = Some(2),
|
||||
Some(VirtualKeyCode::Key3) => new_filter = Some(3),
|
||||
Some(VirtualKeyCode::Key4) => new_filter = Some(4),
|
||||
Some(VirtualKeyCode::Key5) => new_filter = Some(5),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.gamepads[0] |= gamepad_button(&input);
|
||||
} else {
|
||||
self.gamepads[0] &= !gamepad_button(&input);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawEventsCleared => {
|
||||
if Instant::now() >= self.next_frame
|
||||
// workaround needed on Wayland until the next winit release
|
||||
&& self.window.fullscreen().is_some() == self.is_fullscreen
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if let Some(new_filter) = new_filter {
|
||||
self.filter = create_filter(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
new_filter,
|
||||
);
|
||||
}
|
||||
});
|
||||
Input {
|
||||
gamepads: self.gamepads,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
self.palette_screen_mode.resolve_screen(&mut encoder);
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
self.filter.render(&mut render_pass);
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
self.is_open
|
||||
}
|
||||
}
|
||||
|
||||
fn create_filter(
|
||||
device: &wgpu::Device,
|
||||
screen_texture: &wgpu::TextureView,
|
||||
window_size: PhysicalSize<u32>,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
filter: u32,
|
||||
) -> Box<dyn Filter> {
|
||||
match filter {
|
||||
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 {
|
||||
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>);
|
||||
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 {
|
||||
framebuffer: wgpu::Texture,
|
||||
palette: wgpu::Texture,
|
||||
screen_view: wgpu::TextureView,
|
||||
bind_group: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl PaletteScreenMode {
|
||||
fn new(device: &wgpu::Device) -> PaletteScreenMode {
|
||||
let framebuffer_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size: wgpu::Extent3d {
|
||||
width: 320,
|
||||
height: 240,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R8Uint,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
label: None,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size: wgpu::Extent3d {
|
||||
width: 256,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D1,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
label: None,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size: wgpu::Extent3d {
|
||||
width: 320,
|
||||
height: 240,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
label: None,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let framebuffer_texture_view =
|
||||
framebuffer_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let palette_texture_view =
|
||||
palette_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let screen_texture_view =
|
||||
screen_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let palette_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Uint,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D1,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let palette_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &palette_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&framebuffer_texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&palette_texture_view),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let palette_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("palette.wgsl").into()),
|
||||
});
|
||||
|
||||
let palette_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&palette_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let palette_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&palette_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &palette_shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &palette_shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: Default::default(),
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
PaletteScreenMode {
|
||||
framebuffer: framebuffer_texture,
|
||||
palette: palette_texture,
|
||||
screen_view: screen_texture_view,
|
||||
bind_group: palette_bind_group,
|
||||
pipeline: palette_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_framebuffer(&self, queue: &wgpu::Queue, pixels: &[u8]) {
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &self.framebuffer,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&bytemuck::cast_slice(pixels),
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(320),
|
||||
rows_per_image: None,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: 320,
|
||||
height: 240,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn write_palette(&self, queue: &wgpu::Queue, palette: &[u8]) {
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &self.palette,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&bytemuck::cast_slice(palette),
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(256 * 4),
|
||||
rows_per_image: None,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: 256,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn resolve_screen(&self, encoder: &mut wgpu::CommandEncoder) {
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.screen_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
24
uw8-window/src/gpu/palette.wgsl
Normal file
24
uw8-window/src/gpu/palette.wgsl
Normal file
@@ -0,0 +1,24 @@
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let x = (1.0 - f32(vertex_index)) * 3.0;
|
||||
let y = f32(vertex_index & 1u) * 3.0 - 1.0;
|
||||
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
|
||||
out.tex_coords = vec2<f32>((x + 1.0) * 160.0, (y + 1.0) * 120.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var framebuffer_texture: texture_2d<u32>;
|
||||
@group(0) @binding(1) var palette_texture: texture_1d<f32>;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let texel = vec2<i32>(floor(in.tex_coords));
|
||||
let index = textureLoad(framebuffer_texture, texel, 0).r;
|
||||
return textureLoad(palette_texture, i32(index), 0);
|
||||
}
|
||||
157
uw8-window/src/gpu/square.rs
Normal file
157
uw8-window/src/gpu/square.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use super::Filter;
|
||||
|
||||
pub struct SquareFilter {
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl SquareFilter {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
screen: &wgpu::TextureView,
|
||||
resolution: PhysicalSize<u32>,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
) -> SquareFilter {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(resolution),
|
||||
};
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&screen),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("square.wgsl").into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: Default::default(),
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
SquareFilter {
|
||||
uniform_buffer,
|
||||
bind_group,
|
||||
pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for SquareFilter {
|
||||
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
|
||||
let uniforms = Uniforms {
|
||||
texture_scale: texture_scale_from_resolution(new_size),
|
||||
};
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_scale_from_resolution(res: PhysicalSize<u32>) -> [f32; 4] {
|
||||
let scale = ((res.width as f32) / 160.0).min((res.height as f32) / 120.0);
|
||||
[
|
||||
scale / res.width as f32,
|
||||
scale / res.height as f32,
|
||||
2.0 / scale,
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
texture_scale: [f32; 4],
|
||||
}
|
||||
44
uw8-window/src/gpu/square.wgsl
Normal file
44
uw8-window/src/gpu/square.wgsl
Normal file
@@ -0,0 +1,44 @@
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct Uniforms {
|
||||
texture_scale: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) in_vertex_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let i = in_vertex_index / 3u + in_vertex_index % 3u;
|
||||
let x = 0.0 + f32(i % 2u) * 320.0;
|
||||
let y = 0.0 + f32(i / 2u) * 240.0;
|
||||
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0);
|
||||
out.tex_coords = vec2<f32>(x, y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var linear_sampler: sampler;
|
||||
|
||||
fn aa_tex_coord(c: f32) -> f32 {
|
||||
let low = c - uniforms.texture_scale.z * 0.5;
|
||||
let high = c + uniforms.texture_scale.z * 0.5;
|
||||
let base = floor(low);
|
||||
let center = base + 0.5;
|
||||
let next = base + 1.0;
|
||||
if high > next {
|
||||
return center + (high - next) / (high - low);
|
||||
} else {
|
||||
return center;
|
||||
}
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(screen_texture, linear_sampler, vec2<f32>(aa_tex_coord(in.tex_coords.x), aa_tex_coord(in.tex_coords.y)) / vec2<f32>(320.0, 240.0));
|
||||
}
|
||||
124
uw8-window/src/lib.rs
Normal file
124
uw8-window/src/lib.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use anyhow::Result;
|
||||
use std::time::Instant;
|
||||
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
|
||||
pub struct Window {
|
||||
inner: Box<dyn WindowImpl>,
|
||||
fps_counter: Option<FpsCounter>,
|
||||
}
|
||||
|
||||
struct FpsCounter {
|
||||
start: Instant,
|
||||
num_frames: u32,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(mut config: WindowConfig) -> Result<Window> {
|
||||
let fps_counter = if config.fps_counter {
|
||||
Some(FpsCounter {
|
||||
start: Instant::now(),
|
||||
num_frames: 0,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
config.scale = config.scale.max(1.).min(20.);
|
||||
if config.enable_gpu {
|
||||
match gpu::Window::new(config) {
|
||||
Ok(window) => {
|
||||
return Ok(Window {
|
||||
inner: Box::new(window),
|
||||
fps_counter,
|
||||
})
|
||||
}
|
||||
Err(err) => eprintln!(
|
||||
"Failed to create gpu window: {}\nFalling back tp cpu window",
|
||||
err
|
||||
),
|
||||
}
|
||||
}
|
||||
cpu::Window::new().map(|window| Window {
|
||||
inner: Box::new(window),
|
||||
fps_counter,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self) -> Input {
|
||||
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,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl Default for WindowConfig {
|
||||
fn default() -> WindowConfig {
|
||||
WindowConfig {
|
||||
enable_gpu: true,
|
||||
filter: 5,
|
||||
fullscreen: false,
|
||||
fps_counter: false,
|
||||
scale: 2.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
self.scale = args
|
||||
.opt_value_from_str("--scale")
|
||||
.unwrap()
|
||||
.unwrap_or(self.scale);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
59
uw8-window/src/main.rs
Normal file
59
uw8-window/src/main.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::time::Instant;
|
||||
use uw8_window::WindowConfig;
|
||||
|
||||
fn main() {
|
||||
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 start_time = Instant::now();
|
||||
|
||||
let mut palette = vec![0u32; 256];
|
||||
for i in 0..256 {
|
||||
let v = i & 15;
|
||||
let r = ((i >> 2) & 12) * v;
|
||||
let g = ((i >> 3) & 12) * v;
|
||||
let b = ((i >> 4) & 12) * v;
|
||||
palette[i as usize] = r + (g << 8) + (b << 16);
|
||||
}
|
||||
|
||||
let mut fps_start = Instant::now();
|
||||
let mut fps_counter = 0;
|
||||
|
||||
let mut window_config = WindowConfig::default();
|
||||
window_config.parse_arguments(&mut args);
|
||||
|
||||
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();
|
||||
}
|
||||
draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32());
|
||||
window.end_frame(&framebuffer, bytemuck::cast_slice(&palette), Instant::now());
|
||||
|
||||
fps_counter += 1;
|
||||
let elapsed = fps_start.elapsed().as_secs_f32();
|
||||
if elapsed >= 1.0 {
|
||||
println!("{:.1} fps", fps_counter as f32 / elapsed);
|
||||
fps_start = Instant::now();
|
||||
fps_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_frame(framebuffer: &mut [u8], time: f32) {
|
||||
for x in 0..320 {
|
||||
let xr = x as f32 - 160.0;
|
||||
for y in 0..240 {
|
||||
let yr = y as f32 - 120.0;
|
||||
let f = 8192.0 / (xr * xr + yr * yr);
|
||||
let u = xr * f + 512.0 + time * 32.0;
|
||||
let v = yr * f + time * 29.0;
|
||||
let c = (u.floor() as i32 ^ v.floor() as i32) as u32;
|
||||
framebuffer[x + y * 320] = c as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
web/opus-repro.html
Normal file
57
web/opus-repro.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<html>
|
||||
<button onclick="go()">Go!</button>
|
||||
<canvas id="screen" width="320" height="240"></canvas>
|
||||
<video id="video"></video>
|
||||
<script>
|
||||
function go() {
|
||||
let audioContext = new AudioContext({sampleRate: 44100});
|
||||
|
||||
let oscillator = new OscillatorNode(audioContext);
|
||||
let gain = new GainNode(audioContext, {gain: 1});
|
||||
oscillator.connect(gain);
|
||||
gain.connect(audioContext.destination);
|
||||
for(let i = 0; i < 8; ++i ) {
|
||||
gain.gain.setValueAtTime(1, i / 2);
|
||||
gain.gain.setValueAtTime(0, i / 2 + 0.3);
|
||||
}
|
||||
oscillator.start();
|
||||
oscillator.stop(4);
|
||||
|
||||
let screen = document.getElementById('screen');
|
||||
let context = screen.getContext('2d');
|
||||
let startTime = Date.now();
|
||||
let drawFrame = () => {
|
||||
let time = Date.now() - startTime;
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, 320, 240);
|
||||
if(time < 4000) {
|
||||
if(time % 500 < 300) {
|
||||
context.fillStyle = 'black';
|
||||
context.fillRect(time / 15, 50, 50, 50);
|
||||
}
|
||||
window.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
};
|
||||
drawFrame();
|
||||
|
||||
let stream = screen.captureStream();
|
||||
let audioStreamNode = audioContext.createMediaStreamDestination();
|
||||
gain.connect(audioStreamNode);
|
||||
stream.addTrack(audioStreamNode.stream.getAudioTracks()[0]);
|
||||
let recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
|
||||
|
||||
let chunks = [];
|
||||
recorder.ondataavailable = e => chunks.push(e.data);
|
||||
recorder.onstop = () => {
|
||||
let blob = new Blob(chunks, {type: 'video/webm'});
|
||||
let url = URL.createObjectURL(blob);
|
||||
let video = document.getElementById('video');
|
||||
video.src = url;
|
||||
video.play();
|
||||
};
|
||||
|
||||
recorder.start();
|
||||
setTimeout(() => recorder.stop(), 4000);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
@@ -4,5 +4,6 @@
|
||||
"@parcel/optimizer-data-url": "^2.0.0",
|
||||
"@parcel/transformer-inline-string": "^2.0.0",
|
||||
"parcel": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
2
web/run
Executable file
2
web/run
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
rm -rf .parcel-cache && yarn parcel src/index.html
|
||||
94
web/src/audiolet.js
Normal file
94
web/src/audiolet.js
Normal file
@@ -0,0 +1,94 @@
|
||||
let U8 = (...a) => new Uint8Array(...a);
|
||||
class APU extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.sampleIndex = 0;
|
||||
this.currentTime = 0;
|
||||
this.isFirstMessage = true;
|
||||
this.pendingUpdates = [];
|
||||
this.port.onmessage = (ev) => {
|
||||
if(this.memory) {
|
||||
if(this.isFirstMessage)
|
||||
{
|
||||
this.currentTime += (ev.data.t - this.currentTime) / 8;
|
||||
this.isFirstMessage = false;
|
||||
}
|
||||
this.pendingUpdates.push(ev.data);
|
||||
} else {
|
||||
this.load(ev.data[0], ev.data[1]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async load(platform_data, data) {
|
||||
let memory = new WebAssembly.Memory({ initial: 4, maximum: 4 });
|
||||
|
||||
let importObject = {
|
||||
env: {
|
||||
memory
|
||||
},
|
||||
};
|
||||
|
||||
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
|
||||
importObject.env[n] = Math[n];
|
||||
}
|
||||
|
||||
for (let i = 9; i < 64; ++i) {
|
||||
importObject.env['reserved' + i] = () => { };
|
||||
}
|
||||
|
||||
let logLine = '';
|
||||
importObject.env['logChar'] = (c) => {
|
||||
if(c == 10) {
|
||||
console.log(logLine);
|
||||
logLine = '';
|
||||
} else {
|
||||
logLine += String.fromCharCode(c);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
importObject.env['g_reserved' + i] = 0;
|
||||
}
|
||||
|
||||
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
|
||||
|
||||
let platform_instance = await instantiate(platform_data);
|
||||
|
||||
for (let name in platform_instance.exports) {
|
||||
importObject.env[name] = platform_instance.exports[name]
|
||||
}
|
||||
|
||||
let instance = await instantiate(data);
|
||||
|
||||
this.memory = memory;
|
||||
|
||||
this.snd = instance.exports.snd || platform_instance.exports.sndGes;
|
||||
|
||||
this.port.postMessage(2);
|
||||
}
|
||||
|
||||
process(inputs, outputs, parameters) {
|
||||
this.isFirstMessage = true;
|
||||
if(this.snd) {
|
||||
while(this.pendingUpdates.length > 0 && this.pendingUpdates[0].t <= this.currentTime) {
|
||||
U8(this.memory.buffer, 80, 32).set(U8(this.pendingUpdates.shift().r));
|
||||
}
|
||||
let u32Mem = new Uint32Array(this.memory.buffer);
|
||||
u32Mem[16] = this.currentTime;
|
||||
let channels = outputs[0];
|
||||
let index = this.sampleIndex;
|
||||
let numSamples = channels[0].length;
|
||||
for(let i = 0; i < numSamples; ++i) {
|
||||
channels[0][i] = this.snd(index++);
|
||||
channels[1][i] = this.snd(index++);
|
||||
}
|
||||
this.sampleIndex = index & 0xffffffff;
|
||||
this.currentTime += numSamples / 44.1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('apu', APU);
|
||||
@@ -10,11 +10,12 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="uw8">
|
||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.1
|
||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.4.0
|
||||
</div>
|
||||
<div id="centered">
|
||||
<canvas id="screen" width="320" height="240">
|
||||
<canvas class="screen" id="screen" width="320" height="240">
|
||||
</canvas>
|
||||
<button class="screen" id="start" style="display:none">Click to start</button>
|
||||
<div id="timer" hidden="true"></div>
|
||||
<div id="message"></div>
|
||||
<button id="cartButton" style="visibility:hidden">Load cart...</button>
|
||||
|
||||
@@ -12,6 +12,7 @@ let uw8 = MicroW8(document.getElementById('screen'), {
|
||||
setMessage,
|
||||
keyboardElement: window,
|
||||
timerElement: document.getElementById("timer"),
|
||||
startButton: document.getElementById("start")
|
||||
});
|
||||
|
||||
function runModuleFromHash() {
|
||||
@@ -79,7 +80,9 @@ if(location.hash.length != 0) {
|
||||
url += 'cart.uw8';
|
||||
}
|
||||
try {
|
||||
await uw8.runModuleFromURL(url, true);
|
||||
if(!await uw8.runModuleFromURL(url, true)) {
|
||||
setupLoad();
|
||||
}
|
||||
} catch(e) {
|
||||
setupLoad();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import loaderUrl from "data-url:../../platform/bin/loader.wasm";
|
||||
import platformUrl from "data-url:../../platform/bin/platform.uw8";
|
||||
import audioWorkletUrl from "data-url:./audiolet.js";
|
||||
|
||||
class AudioNode extends AudioWorkletNode {
|
||||
constructor(context) {
|
||||
super(context, 'apu', {outputChannelCount: [2]});
|
||||
}
|
||||
}
|
||||
|
||||
let U8 = (...a) => new Uint8Array(...a);
|
||||
let U32 = (...a) => new Uint32Array(...a);
|
||||
|
||||
export default function MicroW8(screen, config = {}) {
|
||||
if(!config.setMessage) {
|
||||
@@ -18,9 +28,6 @@ export default function MicroW8(screen, config = {}) {
|
||||
|
||||
let currentData;
|
||||
|
||||
let U8 = (d) => new Uint8Array(d);
|
||||
let U32 = (d) => new Uint32Array(d);
|
||||
|
||||
let pad = 0;
|
||||
let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement;
|
||||
if(keyboardElement) {
|
||||
@@ -84,12 +91,24 @@ export default function MicroW8(screen, config = {}) {
|
||||
keyboardElement.onkeyup = keyHandler;
|
||||
}
|
||||
|
||||
let audioContext;
|
||||
let audioNode;
|
||||
|
||||
async function runModule(data, keepUrl) {
|
||||
if (cancelFunction) {
|
||||
cancelFunction();
|
||||
cancelFunction = null;
|
||||
}
|
||||
|
||||
audioContext = new AudioContext({sampleRate: 44100});
|
||||
let keepRunning = true;
|
||||
let abortController = new AbortController();
|
||||
cancelFunction = () => {
|
||||
audioContext.close();
|
||||
keepRunning = false;
|
||||
abortController.abort();
|
||||
};
|
||||
|
||||
let cartridgeSize = data.byteLength;
|
||||
|
||||
config.setMessage(cartridgeSize);
|
||||
@@ -97,6 +116,39 @@ export default function MicroW8(screen, config = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
await audioContext.audioWorklet.addModule(audioWorkletUrl);
|
||||
audioNode = new AudioNode(audioContext);
|
||||
|
||||
let audioReadyFlags = 0;
|
||||
let audioReadyResolve;
|
||||
let audioReadyPromise = new Promise(resolve => audioReadyResolve = resolve);
|
||||
let updateAudioReady = (f) => {
|
||||
audioReadyFlags |= f;
|
||||
if(audioReadyFlags == 3 && audioReadyResolve) {
|
||||
audioReadyResolve(true);
|
||||
audioReadyResolve = null;
|
||||
}
|
||||
};
|
||||
let audioStateChange = () => {
|
||||
if(audioContext.state == 'suspended') {
|
||||
if(config.startButton) {
|
||||
config.startButton.style = '';
|
||||
screen.style = 'display:none';
|
||||
}
|
||||
(config.startButton || screen).onclick = () => {
|
||||
audioContext.resume();
|
||||
};
|
||||
} else {
|
||||
if(config.startButton) {
|
||||
config.startButton.style = 'display:none';
|
||||
screen.style = '';
|
||||
}
|
||||
updateAudioReady(1);
|
||||
}
|
||||
};
|
||||
audioContext.onstatechange = audioStateChange;
|
||||
audioStateChange();
|
||||
|
||||
currentData = data;
|
||||
|
||||
let newURL = window.location.pathname;
|
||||
@@ -119,7 +171,7 @@ export default function MicroW8(screen, config = {}) {
|
||||
if(!devkitMode) {
|
||||
memSize.maximum = 4;
|
||||
}
|
||||
let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
|
||||
let memory = new WebAssembly.Memory(memSize);
|
||||
let memU8 = U8(memory.buffer);
|
||||
|
||||
let importObject = {
|
||||
@@ -142,9 +194,9 @@ export default function MicroW8(screen, config = {}) {
|
||||
|
||||
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
|
||||
|
||||
let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer()));
|
||||
let loadModuleURL = async (url) => loadModuleData(await (await fetch(url)).arrayBuffer());
|
||||
|
||||
loader = await loadModuleURL(loaderUrl);
|
||||
loader = await instantiate(await loadModuleURL(loaderUrl));
|
||||
|
||||
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
|
||||
importObject.env[n] = Math[n];
|
||||
@@ -154,13 +206,28 @@ export default function MicroW8(screen, config = {}) {
|
||||
importObject.env['reserved' + i] = () => { };
|
||||
}
|
||||
|
||||
let logLine = '';
|
||||
importObject.env['logChar'] = (c) => {
|
||||
if(c == 10) {
|
||||
console.log(logLine);
|
||||
logLine = '';
|
||||
} else {
|
||||
logLine += String.fromCharCode(c);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
importObject.env['g_reserved' + i] = 0;
|
||||
}
|
||||
|
||||
data = loadModuleData(data);
|
||||
|
||||
let platform_instance = await loadModuleURL(platformUrl);
|
||||
let platform_data = await loadModuleURL(platformUrl);
|
||||
|
||||
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
|
||||
audioNode.port.postMessage([platform_data, data]);
|
||||
|
||||
let platform_instance = await instantiate(platform_data);
|
||||
|
||||
for (let name in platform_instance.exports) {
|
||||
importObject.env[name] = platform_instance.exports[name]
|
||||
@@ -170,13 +237,36 @@ export default function MicroW8(screen, config = {}) {
|
||||
|
||||
let buffer = U32(imageData.data.buffer);
|
||||
|
||||
let startTime = Date.now();
|
||||
await audioReadyPromise;
|
||||
|
||||
let keepRunning = true;
|
||||
cancelFunction = () => keepRunning = false;
|
||||
let startTime = Date.now();
|
||||
let frameCounter = 0;
|
||||
|
||||
const timePerFrame = 1000 / 60;
|
||||
let nextFrame = startTime;
|
||||
|
||||
audioNode.connect(audioContext.destination);
|
||||
|
||||
let isPaused = false;
|
||||
let pauseTime = startTime;
|
||||
let updateVisibility = isVisible => {
|
||||
let now = Date.now();
|
||||
if(isVisible) {
|
||||
isPaused = false;
|
||||
audioContext.resume();
|
||||
startTime += now - pauseTime;
|
||||
} else {
|
||||
isPaused = true;
|
||||
audioContext.suspend();
|
||||
pauseTime = now;
|
||||
}
|
||||
};
|
||||
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
|
||||
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
|
||||
updateVisibility(document.hasFocus());
|
||||
|
||||
if (instance.exports.start) {
|
||||
instance.exports.start();
|
||||
}
|
||||
|
||||
function mainloop() {
|
||||
if (!keepRunning) {
|
||||
@@ -184,9 +274,9 @@ export default function MicroW8(screen, config = {}) {
|
||||
}
|
||||
|
||||
try {
|
||||
let now = Date.now();
|
||||
let restart = false;
|
||||
if (now >= nextFrame) {
|
||||
let thisFrame;
|
||||
if (!isPaused) {
|
||||
let gamepads = navigator.getGamepads();
|
||||
let gamepad = 0;
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
@@ -214,23 +304,37 @@ export default function MicroW8(screen, config = {}) {
|
||||
}
|
||||
|
||||
let u32Mem = U32(memory.buffer);
|
||||
u32Mem[16] = now - startTime;
|
||||
let time = Date.now() - startTime;
|
||||
u32Mem[16] = time;
|
||||
u32Mem[17] = pad | gamepad;
|
||||
u32Mem[18] = frameCounter++;
|
||||
if(instance.exports.upd) {
|
||||
instance.exports.upd();
|
||||
}
|
||||
platform_instance.exports.endFrame();
|
||||
|
||||
let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024));
|
||||
let soundRegisters = new ArrayBuffer(32);
|
||||
U8(soundRegisters).set(U8(memory.buffer, 80, 32));
|
||||
audioNode.port.postMessage({t: time, r: soundRegisters}, [soundRegisters]);
|
||||
|
||||
let palette = U32(memory.buffer, 0x13000, 1024);
|
||||
for (let i = 0; i < 320 * 240; ++i) {
|
||||
buffer[i] = palette[memU8[i + 120]] | 0xff000000;
|
||||
}
|
||||
canvasCtx.putImageData(imageData, 0, 0);
|
||||
nextFrame = Math.max(nextFrame + timePerFrame, now);
|
||||
|
||||
let timeOffset = ((time * 6) % 100 - 50) / 6;
|
||||
thisFrame = startTime + time - timeOffset / 8;
|
||||
} else {
|
||||
thisFrame = Date.now();
|
||||
}
|
||||
let now = Date.now();
|
||||
let nextFrame = Math.max(thisFrame + timePerFrame, now);
|
||||
|
||||
if (restart) {
|
||||
runModule(currentData);
|
||||
} else {
|
||||
window.requestAnimationFrame(mainloop);
|
||||
window.setTimeout(mainloop, nextFrame - now)
|
||||
}
|
||||
} catch (err) {
|
||||
config.setMessage(cartridgeSize, err.toString());
|
||||
@@ -253,14 +357,25 @@ export default function MicroW8(screen, config = {}) {
|
||||
|
||||
let videoRecorder;
|
||||
let videoStartTime;
|
||||
let videoAudioSourceNode;
|
||||
let videoAudioStreamNode;
|
||||
function recordVideo() {
|
||||
if(videoRecorder) {
|
||||
videoRecorder.stop();
|
||||
videoRecorder = null;
|
||||
videoAudioSourceNode.disconnect(videoAudioStreamNode);
|
||||
videoAudioSourceNode = null;
|
||||
videoAudioStreamNode = null;
|
||||
return;
|
||||
}
|
||||
|
||||
videoRecorder = new MediaRecorder(screen.captureStream(), {
|
||||
let stream = screen.captureStream();
|
||||
videoAudioStreamNode = audioContext.createMediaStreamDestination();
|
||||
videoAudioSourceNode = audioNode;
|
||||
audioNode.connect(videoAudioStreamNode);
|
||||
stream.addTrack(videoAudioStreamNode.stream.getAudioTracks()[0]);
|
||||
|
||||
videoRecorder = new MediaRecorder(stream, {
|
||||
mimeType: 'video/webm',
|
||||
videoBitsPerSecond: 25000000
|
||||
});
|
||||
@@ -305,10 +420,11 @@ export default function MicroW8(screen, config = {}) {
|
||||
async function runModuleFromURL(url, keepUrl) {
|
||||
let response = await fetch(url);
|
||||
let type = response.headers.get('Content-Type');
|
||||
if(type && type.includes('html')) {
|
||||
throw false;
|
||||
if((type && type.includes('html')) || response.status != 200) {
|
||||
return false;
|
||||
}
|
||||
runModule(await response.arrayBuffer(), keepUrl || devkitMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -37,7 +37,7 @@ a:hover {
|
||||
color: #405040;
|
||||
}
|
||||
|
||||
#screen {
|
||||
.screen {
|
||||
width: 320px;
|
||||
height: 240px;
|
||||
image-rendering: pixelated;
|
||||
@@ -45,9 +45,16 @@ a:hover {
|
||||
margin-bottom: 8px;
|
||||
border: 4px solid #303040;
|
||||
box-shadow: 5px 5px 20px black;
|
||||
}
|
||||
|
||||
#screen {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
#start {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
#timer::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
@@ -84,21 +91,21 @@ button:active {
|
||||
}
|
||||
|
||||
@media (min-width: 680px) and (min-height: 620px) {
|
||||
#screen {
|
||||
.screen {
|
||||
width: 640px;
|
||||
height: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) and (min-height: 800px) {
|
||||
#screen {
|
||||
.screen {
|
||||
width: 960px;
|
||||
height: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width:640px) and (height:480px) {
|
||||
#screen {
|
||||
.screen {
|
||||
width: 640px;
|
||||
height: 480px;
|
||||
border: 0;
|
||||
|
||||
4981
web/yarn.lock
4981
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user