mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
Compare commits
156 Commits
a41f9ebe4d
...
v0.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 44b8656f29 | |||
| 90467f7c5b | |||
| c56196bd2e | |||
| 266493ca1c | |||
| 4c75ba2e44 | |||
| f1493ebded | |||
| 5d41733142 | |||
| c25a52b61b | |||
| 619ea903ba | |||
| 9b900a49e4 | |||
| f21497dd2e | |||
| 381eaf970f | |||
| 9632adb57f | |||
| 33e08e9b73 | |||
| cacde9136c | |||
| 81a38c2d75 | |||
| c9ae04e652 | |||
| 179b3eaed1 | |||
| b7d0004307 | |||
| b34685b1c2 | |||
| 3d79239a01 | |||
| f062e545f6 | |||
| 68890e62ad | |||
| 10ba7ed3bb | |||
| acea5cb6e0 | |||
| d239775411 | |||
| 20365a0dd0 | |||
| 88ee0e1bef | |||
| 26206a312a | |||
| 6a75988489 | |||
| 397ff19d80 | |||
| b7e8ddf0f1 | |||
| ed9d9fdeb5 | |||
| cd1275a78f | |||
| 32345876b9 | |||
| 6ca63b87e5 | |||
| bf7604d385 | |||
| 0f22b1efb9 | |||
| 4521fb73ef | |||
| 6f22e00487 | |||
| cf0e40a0e5 | |||
| 462dc3a1c6 | |||
| 0f795ae061 | |||
| 2eca3fa8c8 | |||
| 7db0d66ad4 | |||
| 28713bd1df |
50
.github/workflows/ci.yml
vendored
Normal file
50
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build: [ linux, windows, macos ]
|
||||||
|
include:
|
||||||
|
- build: linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
exe: uw8
|
||||||
|
- build: windows
|
||||||
|
os: windows-latest
|
||||||
|
exe: uw8.exe
|
||||||
|
- build: macos
|
||||||
|
os: macos-latest
|
||||||
|
exe: uw8
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Cache build dirs
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --verbose
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: uw8-${{ matrix.build }}
|
||||||
|
path: target/release/${{ matrix.exe }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
.cargo/
|
.cargo/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
/examples/**/*.wasm
|
||||||
|
/examples/**/*.uw8
|
||||||
3663
Cargo.lock
generated
3663
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
29
Cargo.toml
29
Cargo.toml
@@ -1,16 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.1.0"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["native", "browser"]
|
||||||
|
native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
|
||||||
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = "0.30"
|
wasmtime = { version = "5.0.0", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = "0.19"
|
env_logger = "0.10"
|
||||||
notify = "4"
|
log = "0.4"
|
||||||
pico-args = "0.4"
|
uw8-window = { path = "uw8-window", optional = true }
|
||||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b" }
|
notify = "5"
|
||||||
|
pico-args = "0.5"
|
||||||
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
|
||||||
wat = "1"
|
wat = "1"
|
||||||
uw8-tool = { path = "uw8-tool" }
|
uw8-tool = { path = "uw8-tool" }
|
||||||
|
same-file = "1"
|
||||||
|
warp = { version = "0.3.3", optional = true }
|
||||||
|
tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
|
||||||
|
tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
|
||||||
|
webbrowser = { version = "0.8.6", optional = true }
|
||||||
|
ansi_term = "0.12.1"
|
||||||
|
cpal = { version = "0.14.2", optional = true }
|
||||||
|
rubato = { version = "0.12.0", optional = true }
|
||||||
|
|||||||
104
README.md
104
README.md
@@ -1 +1,103 @@
|
|||||||
See [here](https://exoticorn.github.io/microw8/).
|
# MicroW8
|
||||||
|
|
||||||
|
MicroW8 is a WebAssembly based fantasy console inspired by the likes of [TIC-80](https://tic80.com/), [WASM-4](https://wasm4.org/) and [PICO-8](https://www.lexaloffle.com/pico-8.php).
|
||||||
|
|
||||||
|
The initial motivation behind MicroW8 was to explore whether there was a way to make WebAssembly viable for size-coding. (Size coding being the art of creating tiny (often <= 256 bytes) graphical effects and games.) The available examples so far are all in this space, however, I very carefully made sure that all design decisions make sense from the point of view of bigger projects as well.
|
||||||
|
|
||||||
|
See [here](https://exoticorn.github.io/microw8/) for more information and docs.
|
||||||
|
|
||||||
|
## Specs
|
||||||
|
|
||||||
|
* Screen: 320x240, 256 colors, 60Hz
|
||||||
|
* Modules: Up to 256KB (WASM)
|
||||||
|
* Memory: 256KB
|
||||||
|
* Gamepad input (D-Pad + 4 Buttons)
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
* [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)
|
||||||
|
|
||||||
|
The download includes
|
||||||
|
|
||||||
|
* `microw8.html`: The web runtime, a small, self-contained html file that can be opened in any modern browser to load and run MicroW8 carts.
|
||||||
|
* `uw8`/`uw8.exe`: The MicroW8 dev tool, including a native runtime.
|
||||||
|
* `examples`: Example source code in CurlyWas and Wat (WebAssembly text format).
|
||||||
|
* `carts`: The examples compiled to `.uw8` carts.
|
||||||
|
|
||||||
|
## uw8 dev tool
|
||||||
|
|
||||||
|
```
|
||||||
|
uw8 run [<options>] <file>
|
||||||
|
|
||||||
|
Runs <file> which can be a binary WebAssembly module, an `.uw8` cart, a wat (WebAssembly text format) source file or a CurlyWas source file.
|
||||||
|
|
||||||
|
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.
|
||||||
|
-u, --uncompressed : Use the uncompressed uw8 format for packing.
|
||||||
|
-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>
|
||||||
|
|
||||||
|
Packs the WebAssembly module or text file, or CurlyWas source file into a .uw8 cart.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-u, --uncompressed : Use the uncompressed uw8 format for packing.
|
||||||
|
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
|
||||||
|
|
||||||
|
|
||||||
|
uw8 unpack <infile> <outfile>
|
||||||
|
|
||||||
|
Unpacks a MicroW8 module into a standard WebAssembly module.
|
||||||
|
|
||||||
|
|
||||||
|
uw8 compile [<options>] <infile> <outfile>
|
||||||
|
|
||||||
|
Compiles a CurlyWas source file to a standard WebAssembly module. Most useful together with
|
||||||
|
the --debug option to get a module that works well in the Chrome debugger.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-d, --debug : Generate a name section to help debugging
|
||||||
|
|
||||||
|
|
||||||
|
uw8 filter-exports <infile> <outfile>
|
||||||
|
|
||||||
|
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
* [Fireworks](https://exoticorn.github.io/microw8/v0.1pre5#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
||||||
|
* [Skip Ahead](https://exoticorn.github.io/microw8/v0.1pre5#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](https://exoticorn.github.io/microw8/v0.1pre4#Ag95rdCB5Ww5NofyQaKF4P1mrNRso4azgiem4hK99Gh8OMzSpFq3NsNDo7O7pqln10D11l9uXr/ritw7OEzKwbEfCdvaRnS2Z0Kz0iDEZt/gIqOdvFmxsL1MjPQ4XInPbUJpQUonhQq29oP2omFabnQxn0bzoK7mZjcwc5GetHG+hGajkJcRr8oOnjfCol8RD+ha33GYtPnut+GLe4ktzf5UxZwGs6oT9qqC61lRDakN) (177 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
||||||
|
* [Technotunnel](https://exoticorn.github.io/microw8/v0.1pre4#AqL8HeK1M9dn2nWNIF5vaq/Vh64pMt5nJIFoFKpBMPUsGtDtpqjo1JbT9LzPhAxCqJ7Yh4TA6oTGd4xhLowf+cWZMY73+7AZmfXJJsBi4cej/hH+4wlAgxFIrnOYnr/18IpnZbsHf0eGm1BhahX74+cVR0TRmNQmYC7GhCNS3mv/3MJn74lCj7t28aBJPjEZhP9fGXdG2u5Egh/Tjdg=) (158 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
||||||
|
|||||||
6
examples/c/build.sh
Executable file
6
examples/c/build.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry,--export-all,--import-memory,--initial-memory=262144,--global-base=81920,-zstack-size=4096 -o cart.wasm cart.c --target=wasm32 && \
|
||||||
|
uw8 filter-exports cart.wasm cart.wasm && \
|
||||||
|
wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
|
||||||
|
uw8 pack -l 9 cart.wasm cart.uw8
|
||||||
27
examples/c/cart.c
Normal file
27
examples/c/cart.c
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#define IMPORT(MODULE, NAME) __attribute__((import_module(MODULE), import_name(NAME)))
|
||||||
|
|
||||||
|
IMPORT("env", "atan2") extern float atan2(float, float);
|
||||||
|
IMPORT("env", "time") extern float time();
|
||||||
|
|
||||||
|
float sqrt(float v) {
|
||||||
|
return __builtin_sqrt(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FRAMEBUFFER ((unsigned char*)120)
|
||||||
|
|
||||||
|
void upd() {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for( ;; ) {
|
||||||
|
float t = time() * 63.0f;
|
||||||
|
float x = (float)(i % 320 - 160);
|
||||||
|
float y = (float)(i / 320 - 120);
|
||||||
|
float d = 40000.0f / sqrt(x * x + y * y);
|
||||||
|
float u = atan2(x, y) * 512.0f / 3.141f;
|
||||||
|
unsigned char c = (unsigned char)((int)(d + t * 2.0f) ^ (int)(u + t)) >> 4;
|
||||||
|
FRAMEBUFFER[i] = c;
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
if(i >= 320*240) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
examples/curlywas/control.cwa
Normal file
22
examples/curlywas/control.cwa
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
printString(0x20000);
|
||||||
|
}
|
||||||
|
|
||||||
|
data 0x20000 {
|
||||||
|
i8(14, 0xfd, 15, 15, 12) // clear screen to color 0xfd
|
||||||
|
"Top left"
|
||||||
|
i8(14, 10, 11, 11, 11, 11) // scroll up 4 lines
|
||||||
|
i8(31, 28, 29, 14, 0xfd) "Bottom right"
|
||||||
|
i8(14, 10, 10, 10) // scroll down 2 lines
|
||||||
|
i8(31, 40, 3, 14, 10, 15, 0xf0) "Other colors"
|
||||||
|
i8(24, 0xb0) "inverted"
|
||||||
|
i8(13, 10, 8, 8) "->"
|
||||||
|
i8(10, 10, 9, 9, 1) "|<-"
|
||||||
|
i8(5, 31, 7, 28+17, 15, 0xe3) "Graphics text!"
|
||||||
|
i8(5, 31, 6, 28+16, 15, 0xe5) "Graphics text!"
|
||||||
|
i8(4, 24, 14, 10, 0x90, 0x80, 0xf1)
|
||||||
|
i8(31, 37, 29, 0xf1, 0x80, 0x90)
|
||||||
|
i8(0)
|
||||||
|
}
|
||||||
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
|
||||||
|
)
|
||||||
|
}
|
||||||
25
examples/curlywas/fireworks.cwa
Normal file
25
examples/curlywas/fireworks.cwa
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
cls(0);
|
||||||
|
|
||||||
|
let i: i32;
|
||||||
|
loop pixels {
|
||||||
|
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;
|
||||||
|
randomSeed(rocket);
|
||||||
|
let inline x = randomf() * 645 as f32;
|
||||||
|
let y = randomf() * 133 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);
|
||||||
|
circle(
|
||||||
|
x + dx * dist,
|
||||||
|
y + dy * dist + local_time * local_time * 24 as f32,
|
||||||
|
1 as f32, (rocket % 11 + 1) * 16 - (local_time * 7 as f32) as i32 - (i % 4)
|
||||||
|
);
|
||||||
|
branch_if (i := i + 1) < 5120: pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,30 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
global mut mode: i32 = 0;
|
global mut mode: i32 = 0;
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
|
|
||||||
if triggered(4) {
|
if isButtonTriggered(BUTTON_A) {
|
||||||
mode = !mode;
|
mode = !mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextColor(15);
|
setTextColor(15);
|
||||||
printString(mode * 0x20000);
|
printString(mode * USER_MEM);
|
||||||
|
|
||||||
let y: i32;
|
let y: i32;
|
||||||
loop y {
|
loop y {
|
||||||
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
|
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);
|
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
|
||||||
setTextColor(15);
|
setTextColor(15);
|
||||||
setCursor(y * 9 + 16, 24);
|
setCursorPosition(y * 9 + 16, 24);
|
||||||
let lazy hexChar = select(y < 10, y + 48, y + 87);
|
let lazy hexChar = select(y < 10, y + 48, y + 87);
|
||||||
printChar(hexChar);
|
printChar(hexChar);
|
||||||
setCursor(0, y * 9 + 24+16);
|
setCursorPosition(0, y * 9 + 24+16);
|
||||||
printChar(hexChar);
|
printChar(hexChar);
|
||||||
let x = 0;
|
let x = 0;
|
||||||
loop x {
|
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));
|
setTextColor(select(mode, x + y * 16, -9));
|
||||||
if y >= 2 | mode {
|
if y >= 2 | mode {
|
||||||
printChar(select(mode, 0xa4, x + y * 16));
|
printChar(select(mode, 0xa4, x + y * 16));
|
||||||
@@ -47,6 +39,6 @@ data 0 {
|
|||||||
"Default font: (press " i8(0xcc) " for palette)" i8(5, 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)
|
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
|
||||||
}
|
}
|
||||||
@@ -1,102 +1,15 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.cos" fn cos(f32) -> f32;
|
|
||||||
|
|
||||||
fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|
||||||
let swapTmp: f32;
|
|
||||||
if x1 > x2 {
|
|
||||||
swapTmp = x1;
|
|
||||||
x1 = x2;
|
|
||||||
x2 = swapTmp;
|
|
||||||
swapTmp = y1;
|
|
||||||
y1 = y2;
|
|
||||||
y2 = swapTmp;
|
|
||||||
}
|
|
||||||
if x1 < 0 as f32 & x2 >= 0 as f32 {
|
|
||||||
y1 = y1 + (y2 - y1) * -x1 / (x2 - x1);
|
|
||||||
x1 = 0 as f32;
|
|
||||||
}
|
|
||||||
if x1 < 320 as f32 & x2 >= 320 as f32 {
|
|
||||||
y2 = y2 + (y2 - y1) * (320 as f32 - x2) / (x2 - x1);
|
|
||||||
x2 = 320 as f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
if y1 > y2 {
|
|
||||||
swapTmp = x1;
|
|
||||||
x1 = x2;
|
|
||||||
x2 = swapTmp;
|
|
||||||
swapTmp = y1;
|
|
||||||
y1 = y2;
|
|
||||||
y2 = swapTmp;
|
|
||||||
}
|
|
||||||
if y1 < 0 as f32 & y2 >= 0 as f32 {
|
|
||||||
x1 = x1 + (x2 - x1) * -y1 / (y2 - y1);
|
|
||||||
y1 = 0 as f32;
|
|
||||||
}
|
|
||||||
if y1 < 240 as f32 & y2 >= 240 as f32 {
|
|
||||||
x2 = x2 + (x2 - x1) * (240 as f32 - y2) / (y2 - y1);
|
|
||||||
y2 = 240 as f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lazy dx = x2 - x1;
|
|
||||||
let lazy dy = y2 - y1;
|
|
||||||
let max_axis: f32;
|
|
||||||
let p: f32;
|
|
||||||
if abs(dx) >= dy {
|
|
||||||
max_axis = dx;
|
|
||||||
p = x1;
|
|
||||||
} else {
|
|
||||||
max_axis = dy;
|
|
||||||
p = y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
|
||||||
p = floor(p) + 0.5 - p;
|
|
||||||
if max_axis < 0 as f32 {
|
|
||||||
steps = -steps;
|
|
||||||
p = -p;
|
|
||||||
max_axis = -max_axis;
|
|
||||||
}
|
|
||||||
dx = dx / max_axis;
|
|
||||||
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);
|
|
||||||
|
|
||||||
if !steps {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
x1 = x1 + (1 as f32 + p) * dx;
|
|
||||||
y1 = y1 + (1 as f32 + p) * dy;
|
|
||||||
|
|
||||||
p = p + steps as f32;
|
|
||||||
|
|
||||||
loop pixels {
|
|
||||||
if steps := steps - 1 {
|
|
||||||
setPixel(x1 as i32, y1 as i32, col);
|
|
||||||
x1 = x1 + dx;
|
|
||||||
y1 = y1 + dy;
|
|
||||||
branch pixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f = min(max_axis, p) - p;
|
|
||||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
// line(0.0, 4.0, 7.0, -2.0, 15);
|
|
||||||
// return;
|
|
||||||
let i: i32;
|
let i: i32;
|
||||||
loop lines {
|
loop lines {
|
||||||
let angle = i as f32 * (3.1415 / 25.0) + time() * 0.1;
|
let angle = i as f32 * (3.1415 / 25.0) + time() * 0.125;
|
||||||
line(160.0, 120.0, 160.0 + sin(angle) * 100.0, 120.0 + cos(angle) * 100.0, 47);
|
line(
|
||||||
|
160 as f32, 120 as f32,
|
||||||
|
160 as f32 + sin(angle) * 100 as f32,
|
||||||
|
120 as f32 + cos(angle) * 100 as f32,
|
||||||
|
47);
|
||||||
branch_if (i := i + 1) < 50: lines;
|
branch_if (i := i + 1) < 50: lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
global mut pz: i32 = 4;
|
global mut pz: i32 = 4;
|
||||||
global mut px: f32 = 2.0;
|
global mut px: f32 = 2.0;
|
||||||
@@ -16,10 +8,10 @@ global mut f: f32 = 2.0;
|
|||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let y: i32;
|
let y: i32;
|
||||||
let inline zero = 0.0;
|
let inline zero = 0_f;
|
||||||
|
|
||||||
let lazy control_speed = 0.03125;
|
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;
|
f = f * 0.5625;
|
||||||
|
|
||||||
printInt(pz);
|
printInt(pz);
|
||||||
@@ -33,11 +25,13 @@ export fn upd() {
|
|||||||
|
|
||||||
let inline c = (z & 1) * -2;
|
let inline c = (z & 1) * -2;
|
||||||
let inline yf = y as f32;
|
let inline yf = y as f32;
|
||||||
rect(rx, yf, rw, yf / 6 as f32, c + 1);
|
rectangle(rx, yf, rw, yf / 6 as f32, c + 1);
|
||||||
rect(rx, yf, rw, 1 as f32, c - 4);
|
rectangle(rx, yf, rw, 1 as f32, c - 4);
|
||||||
|
|
||||||
if y == 180 & py > zero {
|
if y == 180 & py > zero {
|
||||||
if x > w | x < zero {
|
if x > w | x < zero {
|
||||||
|
0?80 = 0xc3;
|
||||||
|
3?80 = 32;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
py = zero;
|
py = zero;
|
||||||
@@ -51,7 +45,10 @@ export fn upd() {
|
|||||||
circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
|
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);
|
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;
|
py = py + s;
|
||||||
pz = pz + 1;
|
pz = pz + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
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,25 +1,26 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let i: i32;
|
let x: i32;
|
||||||
|
let y: i32;
|
||||||
loop screen {
|
loop screen {
|
||||||
let inline t = time() / 2 as f32;
|
let inline t = time() / 2 as f32;
|
||||||
let lazy o = sin(t) * 0.8;
|
let lazy o = sin(t) * 0.75;
|
||||||
let lazy q = (i % 320) as f32 - 160.1;
|
let inline q = x as f32 - 160.5;
|
||||||
let lazy w = (i / 320 - 120) as f32;
|
let inline w = (y - 120) as f32;
|
||||||
let lazy r = sqrt(q*q + w*w);
|
let lazy r = sqrt(q*q + w*w);
|
||||||
let lazy z = q / r;
|
let lazy z = q / r;
|
||||||
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
|
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
|
||||||
let lazy q2 = (z * s - o) * 10 as f32 + t;
|
let inline q2 = (z * s - o) * 10 as f32 + t;
|
||||||
let lazy w2 = w / r * s * 10 as f32 + t;
|
let inline w2 = w / r * s * 10 as f32 + t;
|
||||||
let lazy s2 = s * 100 as f32 / r;
|
let inline s2 = s * 100 as f32 / r;
|
||||||
i?120 = max(
|
let inline color = max(
|
||||||
0 as f32,
|
0 as f32,
|
||||||
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
|
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
|
||||||
(4 as f32 - s2) as f32
|
(4 as f32 - s2) as f32
|
||||||
) as i32 - 32;
|
) as i32 - 32;
|
||||||
branch_if (i := i + 1) < 320*240: screen
|
setPixel(x, y, color);
|
||||||
|
branch_if x := (x + 1) % 320: screen;
|
||||||
|
branch_if y := (y + 1) % 320: screen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let i: i32;
|
let i: i32;
|
||||||
|
|||||||
16
examples/curlywas/tunnel.cwa
Normal file
16
examples/curlywas/tunnel.cwa
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
let i: i32;
|
||||||
|
loop pixels {
|
||||||
|
let inline t = time() * 63 as f32;
|
||||||
|
let lazy x = (i % 320 - 160) as f32;
|
||||||
|
let lazy y = (i / 320 - 120) as f32;
|
||||||
|
let inline d = 40000 as f32 / sqrt(x * x + y * y);
|
||||||
|
let inline u = atan2(x, y) * (512.0 / 3.141);
|
||||||
|
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
|
||||||
|
i?FRAMEBUFFER = c;
|
||||||
|
|
||||||
|
branch_if (i := i + 1) < 320*240: pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
examples/include/microw8-api.cwa
Normal file
52
examples/include/microw8-api.cwa
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// 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.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
|
||||||
|
import "env.circle_outline" fn circle_outline(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;
|
||||||
|
|
||||||
|
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;
|
||||||
54
examples/include/microw8-api.wat
Normal file
54
examples/include/microw8-api.wat
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
;; 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" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "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)))
|
||||||
|
|
||||||
|
;; 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;
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
rustc --target=wasm32-unknown-unknown --crate-type cdylib -C opt-level="z" -C "link-args=--import-memory --initial-memory=262144 -zstack-size=65536" -o tunnel.wasm tunnel.rs && \
|
rustc --target=wasm32-unknown-unknown -C target-feature=+nontrapping-fptoint --crate-type cdylib -C opt-level="z" -C "link-args=--import-memory --initial-memory=262144 -zstack-size=90000" -o tunnel.wasm tunnel.rs && \
|
||||||
wasm-opt -Oz -o tunnel.wasm tunnel.wasm
|
uw8 filter-exports tunnel.wasm tunnel.wasm && \
|
||||||
|
wasm-opt -Oz --strip-producers -o tunnel.wasm tunnel.wasm && \
|
||||||
|
uw8 pack -l 9 tunnel.wasm tunnel.uw8
|
||||||
|
|||||||
@@ -5,21 +5,23 @@ A nightly rust compiler is needed for the unstable sqrtf32
|
|||||||
intrinsic.
|
intrinsic.
|
||||||
|
|
||||||
Simply compiling with rustc as shown in build.sh results in a
|
Simply compiling with rustc as shown in build.sh results in a
|
||||||
342 byte tunnel.wasm. Using wasm-opt this can be reduced to
|
361 byte tunnel.wasm. Using wasm-opt this can be reduced to
|
||||||
243 bytes.
|
255 bytes.
|
||||||
|
|
||||||
When you disassemble this wasm file using wasm2wat you can see
|
When you disassemble this wasm file using wasm2wat you can see
|
||||||
these globals and exports:
|
these globals and exports:
|
||||||
|
|
||||||
(global (;0;) i32 (i32.const 65536))
|
(global (;0;) i32 (i32.const 90000))
|
||||||
(global (;1;) i32 (i32.const 65536))
|
(global (;1;) i32 (i32.const 90000))
|
||||||
(export "__data_end" (global 0))
|
(export "__data_end" (global 0))
|
||||||
(export "__heap_base" (global 1))
|
(export "__heap_base" (global 1))
|
||||||
|
|
||||||
They are meant to be used for heap allocations and stack for any
|
They are meant to be used for heap allocations and stack for any
|
||||||
values that are not simple scalars (i32, f32, etc.). Since our
|
values that are not simple scalars (i32, f32, etc.). Since our
|
||||||
code doesn't actually use any of that, we can just delete them
|
code doesn't actually use any of that, the globals are only
|
||||||
in a text editor and assemble the code again with wat2wasm.
|
referenced by the exports and we can remove them using
|
||||||
|
'uw8 filter-exports' (preferably before running wasm-opt) which
|
||||||
|
removes all exports except those used by the MicroW8 platform.
|
||||||
|
|
||||||
This gives us a 199 byte wasm file. Running this through
|
This gives us a 211 byte wasm file. Running this through
|
||||||
uw8-tool pack brings us to the final size of 137 bytes.
|
uw8 pack brings us to the final size of 119 bytes.
|
||||||
@@ -9,37 +9,41 @@ mod env {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn atan2(x: f32, y: f32) -> f32;
|
pub fn atan2(x: f32, y: f32) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
pub fn time() -> f32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atan2(x: f32, y: f32) -> f32 {
|
fn atan2(x: f32, y: f32) -> f32 {
|
||||||
unsafe { env::atan2(x, y) }
|
unsafe { env::atan2(x, y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn time() -> f32 {
|
||||||
|
unsafe { env::time() }
|
||||||
|
}
|
||||||
|
|
||||||
fn sqrt(v: f32) -> f32 {
|
fn sqrt(v: f32) -> f32 {
|
||||||
unsafe { core::intrinsics::sqrtf32(v) }
|
unsafe { core::intrinsics::sqrtf32(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ftoi(v: f32) -> i32 {
|
|
||||||
// The compiler is allowed to do bad things to our code if this
|
|
||||||
// ever results in a value that doesn't fit in an i32.
|
|
||||||
// (the joy of undefined behavior)
|
|
||||||
// But that would trap in wasm anyway, so we don't really
|
|
||||||
// care.
|
|
||||||
unsafe { v.to_int_unchecked() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn tic(time: i32) {
|
pub fn upd() {
|
||||||
for i in 0..320 * 240 {
|
let mut i: i32 = 0;
|
||||||
let t = time as f32 / 10 as f32;
|
loop {
|
||||||
|
let t = time() * 63.;
|
||||||
let x = (i % 320 - 160) as f32;
|
let x = (i % 320 - 160) as f32;
|
||||||
let y = (i / 320 - 120) as f32;
|
let y = (i / 320 - 120) as f32;
|
||||||
let d = 40000 as f32 / sqrt(x * x + y * y + 1 as f32);
|
let d = 40000 as f32 / sqrt(x * x + y * y);
|
||||||
let u = atan2(x, y) * 512f32 / 3.141;
|
let u = atan2(x, y) * 512. / 3.141;
|
||||||
let c = (ftoi(d + t * 2 as f32) ^ ftoi(u + t)) as u8;
|
let c = ((d + t * 2.) as i32 ^ (u + t) as i32) as u8 >> 4;
|
||||||
unsafe {
|
unsafe {
|
||||||
*((120 + i) as *mut u8) = c;
|
*((120 + i) as *mut u8) = c;
|
||||||
}
|
}
|
||||||
|
i += 1;
|
||||||
|
if i >= 320*240 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
2
examples/zig/.gitignore
vendored
Normal file
2
examples/zig/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/zig-cache/
|
||||||
|
/zig-out/
|
||||||
41
examples/zig/build.zig
Normal file
41
examples/zig/build.zig
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.build.Builder) void {
|
||||||
|
const mode = std.builtin.Mode.ReleaseSmall;
|
||||||
|
|
||||||
|
const lib = b.addSharedLibrary("cart", "main.zig", .unversioned);
|
||||||
|
lib.setBuildMode(mode);
|
||||||
|
lib.setTarget(.{
|
||||||
|
.cpu_arch = .wasm32,
|
||||||
|
.os_tag = .freestanding,
|
||||||
|
.cpu_features_add = std.Target.wasm.featureSet(&.{ .nontrapping_fptoint })
|
||||||
|
});
|
||||||
|
lib.import_memory = true;
|
||||||
|
lib.initial_memory = 262144;
|
||||||
|
lib.max_memory = 262144;
|
||||||
|
lib.global_base = 81920;
|
||||||
|
lib.stack_size = 8192;
|
||||||
|
lib.install();
|
||||||
|
|
||||||
|
if (lib.install_step) |install_step| {
|
||||||
|
const run_filter_exports = b.addSystemCommand(&[_][]const u8{
|
||||||
|
"uw8", "filter-exports", "zig-out/lib/cart.wasm", "zig-out/lib/cart-filtered.wasm"
|
||||||
|
});
|
||||||
|
run_filter_exports.step.dependOn(&install_step.step);
|
||||||
|
|
||||||
|
const run_wasm_opt = b.addSystemCommand(&[_][]const u8{
|
||||||
|
"wasm-opt", "-Oz", "-o", "zig-out/cart.wasm", "zig-out/lib/cart-filtered.wasm"
|
||||||
|
});
|
||||||
|
run_wasm_opt.step.dependOn(&run_filter_exports.step);
|
||||||
|
|
||||||
|
const run_uw8_pack = b.addSystemCommand(&[_][]const u8{
|
||||||
|
"uw8", "pack", "-l", "9", "zig-out/cart.wasm", "zig-out/cart.uw8"
|
||||||
|
});
|
||||||
|
run_uw8_pack.step.dependOn(&run_wasm_opt.step);
|
||||||
|
|
||||||
|
const make_opt = b.step("make_opt", "make size optimized cart");
|
||||||
|
make_opt.dependOn(&run_uw8_pack.step);
|
||||||
|
|
||||||
|
b.default_step = make_opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
examples/zig/main.zig
Normal file
20
examples/zig/main.zig
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
extern fn atan2(x: f32, y: f32) f32;
|
||||||
|
extern fn time() f32;
|
||||||
|
|
||||||
|
pub const FRAMEBUFFER: *[320*240]u8 = @intToPtr(*[320*240]u8, 120);
|
||||||
|
|
||||||
|
export fn upd() void {
|
||||||
|
var i: u32 = 0;
|
||||||
|
while(true) {
|
||||||
|
var t = time() * 63.0;
|
||||||
|
var x = @intToFloat(f32, (@intCast(i32, i % 320) - 160));
|
||||||
|
var y = @intToFloat(f32, (@intCast(i32, i / 320) - 120));
|
||||||
|
var d = 40000.0 / @sqrt(x * x + y * y);
|
||||||
|
var u = atan2(x, y) * 512.0 / 3.141;
|
||||||
|
var c = @intCast(u8, (@floatToInt(i32, d + t * 2.0) ^ @floatToInt(i32, u + t)) & 255) >> 4;
|
||||||
|
|
||||||
|
FRAMEBUFFER[@as(usize, i)] = c;
|
||||||
|
i += 1;
|
||||||
|
if(i >= 320*240) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
65
logo.svg
Normal file
65
logo.svg
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="logo.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.6041667"
|
||||||
|
inkscape:cx="21.780347"
|
||||||
|
inkscape:cy="63.260116"
|
||||||
|
inkscape:window-width="1916"
|
||||||
|
inkscape:window-height="1041"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="18"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#85a6b2;fill-rule:evenodd;fill-opacity:1"
|
||||||
|
id="rect31"
|
||||||
|
width="112.05161"
|
||||||
|
height="109.64198"
|
||||||
|
x="3.9731982"
|
||||||
|
y="6.3613" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:117.333px;line-height:1.25;font-family:'Fira Code';-inkscape-font-specification:'Fira Code Bold';fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
x="-6.2686396"
|
||||||
|
y="117.70291"
|
||||||
|
id="text2691"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan2689"
|
||||||
|
x="-6.2686396"
|
||||||
|
y="117.70291">W8</tspan></text>
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;stroke-width:1.32327"
|
||||||
|
id="path121"
|
||||||
|
cx="60.962471"
|
||||||
|
cy="6.7644148"
|
||||||
|
r="14.855442" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
313
platform/Cargo.lock
generated
313
platform/Cargo.lock
generated
@@ -30,9 +30,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.47"
|
version = "1.0.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e"
|
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ariadne"
|
name = "ariadne"
|
||||||
@@ -51,9 +51,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.7.2"
|
version = "1.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
@@ -79,9 +79,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chumsky"
|
name = "chumsky"
|
||||||
version = "0.5.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f"
|
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.3.8",
|
"ahash 0.3.8",
|
||||||
]
|
]
|
||||||
@@ -110,9 +110,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
@@ -146,37 +146,44 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "curlywas"
|
name = "curlywas"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ariadne",
|
"ariadne",
|
||||||
"chumsky",
|
"chumsky",
|
||||||
"pico-args",
|
"pico-args 0.4.2",
|
||||||
"wasm-encoder",
|
"wasm-encoder 0.10.0",
|
||||||
"wasmparser",
|
"wasmparser 0.83.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible_collections"
|
name = "fallible_collections"
|
||||||
version = "0.4.3"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eaefd4190151d458f16f0793d3452d7f13aeb3701566a4cefc4c37598876cc00"
|
checksum = "3f57ccc32870366ae684be48b32a1a2e196f98a42a9b4361fe77e13fd4a34755"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -190,13 +197,48 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -210,17 +252,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "lexopt"
|
||||||
version = "0.2.108"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.139"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lodepng"
|
name = "lodepng"
|
||||||
version = "3.4.7"
|
version = "3.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24844d5c0b922ddd52fb5bf0964a4c7f8e799a946ec01bb463771eb04fc1a323"
|
checksum = "f0ad39f75bbaa4b10bb6f2316543632a8046a5bcf9c785488d79720b21f044f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
"fallible_collections",
|
"fallible_collections",
|
||||||
"flate2",
|
"flate2",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -228,13 +277,21 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "log"
|
||||||
version = "0.4.4"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
"autocfg",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -248,9 +305,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pbr"
|
name = "pbr"
|
||||||
@@ -264,12 +321,24 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "pico-args"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platform"
|
name = "platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -287,10 +356,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "proc-macro2"
|
||||||
version = "0.8.29"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a27fa03bb1e3e2941f52d4a555a395a72bf79b0a85fbbaab79447050c97d978c"
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
@@ -304,6 +391,37 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
@@ -324,14 +442,67 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upkr"
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/exoticorn/upkr.git?rev=7d280bd#7d280bd533b037d579b6cdf12fb69046eb5c2c91"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upkr"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cdivsufsort",
|
"cdivsufsort",
|
||||||
"pbr",
|
"lexopt",
|
||||||
"pico-args",
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -340,10 +511,11 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"pbr",
|
"pbr",
|
||||||
"pico-args",
|
"pico-args 0.5.0",
|
||||||
"upkr",
|
"upkr",
|
||||||
"wasm-encoder",
|
"walrus",
|
||||||
"wasmparser",
|
"wasm-encoder 0.22.0",
|
||||||
|
"wasmparser 0.99.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -352,6 +524,32 @@ version = "0.9.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walrus"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"leb128",
|
||||||
|
"log",
|
||||||
|
"walrus-macro",
|
||||||
|
"wasmparser 0.77.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
@@ -360,18 +558,43 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.8.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
||||||
|
dependencies = [
|
||||||
|
"leb128",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.81.0"
|
version = "0.77.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.83.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.99.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
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" }
|
uw8-tool = { path="../uw8-tool" }
|
||||||
anyhow = "1"
|
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.
222
platform/src/ges.cwa
Normal file
222
platform/src/ges.cwa
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
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 phaseShift = (pulseWidth - 128) * 255;
|
||||||
|
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"
|
||||||
@@ -14,9 +14,9 @@ export fn load_uw8(module_size: i32) -> i32 {
|
|||||||
} else {
|
} else {
|
||||||
copy(0x1e000, 0, module_size);
|
copy(0x1e000, 0, module_size);
|
||||||
}
|
}
|
||||||
copy(0, 0x3c800, 8);
|
copy(0, 0x3c200, 8);
|
||||||
|
|
||||||
let base_start = 0x3c808;
|
let base_start = 0x3c208;
|
||||||
let dest = 8;
|
let dest = 8;
|
||||||
let src = 0x1e001;
|
let src = 0x1e001;
|
||||||
|
|
||||||
@@ -43,17 +43,16 @@ export fn load_uw8(module_size: i32) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn section_size(ptr: i32) -> i32 {
|
fn section_size(ptr: i32) -> i32 {
|
||||||
let p = ptr + 1;
|
let p = ptr;
|
||||||
let l = 0;
|
let l: i32;
|
||||||
let shift = 0;
|
let shift: i32;
|
||||||
loop size {
|
loop size {
|
||||||
let lazy b = p?0;
|
let lazy b = (p := p + 1)?0;
|
||||||
l = l | ((b & 127) << shift);
|
l = l | ((b & 127) << shift);
|
||||||
shift = shift + 7;
|
shift = shift + 7;
|
||||||
p = p + 1;
|
branch_if b >> 7: size;
|
||||||
branch_if b & 128: size;
|
|
||||||
}
|
}
|
||||||
p - ptr + l
|
p + 1 - ptr + l
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_section(dest: i32, src: i32) -> i32 {
|
fn copy_section(dest: i32, src: i32) -> i32 {
|
||||||
@@ -63,10 +62,10 @@ fn copy_section(dest: i32, src: i32) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn copy(dest: i32, src: i32, len: i32) {
|
fn copy(dest: i32, src: i32, len: i32) {
|
||||||
if len > 0 {
|
loop bytes {
|
||||||
loop bytes {
|
if len > 0 {
|
||||||
(dest + (len := len - 1))?0 = (src + len)?0;
|
(dest + (len := len - 1))?0 = (src + len)?0;
|
||||||
branch_if len: bytes
|
branch bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,20 +83,21 @@ export fn uncompress(src_ptr: i32, dest_ptr: i32) -> i32 {
|
|||||||
|
|
||||||
let offset: i32;
|
let offset: i32;
|
||||||
|
|
||||||
let byte: i32;
|
|
||||||
|
|
||||||
let i: i32;
|
let i: i32;
|
||||||
loop init_contexts {
|
loop init_contexts {
|
||||||
i!0x3c000 = 0x800;
|
i?0x3c000 = 0x80;
|
||||||
branch_if (i := i + 4) < (256 + 1 + 128) * 4: init_contexts
|
branch_if (i := i + 1) < 256 + 1 + 128: init_contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prev_was_match: i32;
|
||||||
|
|
||||||
block finished {
|
block finished {
|
||||||
loop unpack_loop {
|
loop unpack_loop {
|
||||||
if upkr_bit(0) {
|
let lazy is_match = upkr_bit(0);
|
||||||
if upkr_bit(256) {
|
if is_match {
|
||||||
offset = upkr_length(257) - 1;
|
let inline new_offset = if prev_was_match { 1 } else { upkr_bit(256) };
|
||||||
branch_if !offset: finished
|
if new_offset {
|
||||||
|
branch_if !(offset := upkr_length(257) - 1): finished;
|
||||||
}
|
}
|
||||||
let length = upkr_length(257 + 64);
|
let length = upkr_length(257 + 64);
|
||||||
loop copy {
|
loop copy {
|
||||||
@@ -107,15 +107,14 @@ export fn uncompress(src_ptr: i32, dest_ptr: i32) -> i32 {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// literal
|
// literal
|
||||||
i = 0;
|
let byte = 1;
|
||||||
byte = 1;
|
|
||||||
loop literal {
|
loop literal {
|
||||||
byte = (byte << 1) | upkr_bit(byte);
|
branch_if (byte := (byte << 1) | upkr_bit(byte)) < 256: literal;
|
||||||
branch_if (i := i + 1) < 8: literal;
|
|
||||||
}
|
}
|
||||||
dest_ptr?0 = byte;
|
dest_ptr?0 = byte;
|
||||||
dest_ptr = dest_ptr + 1;
|
dest_ptr = dest_ptr + 1;
|
||||||
}
|
}
|
||||||
|
prev_was_match = is_match;
|
||||||
branch unpack_loop;
|
branch unpack_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,9 +126,8 @@ fn upkr_length(context_index: i32) -> i32 {
|
|||||||
let length: i32;
|
let length: i32;
|
||||||
let bit_pos: i32;
|
let bit_pos: i32;
|
||||||
loop bits {
|
loop bits {
|
||||||
if upkr_bit(context_index) {
|
if upkr_bit(context_index + bit_pos) {
|
||||||
length = length | (upkr_bit(context_index + 1) << bit_pos);
|
length = length | (upkr_bit(context_index + bit_pos + 32) << bit_pos);
|
||||||
context_index = context_index + 2;
|
|
||||||
bit_pos = bit_pos + 1;
|
bit_pos = bit_pos + 1;
|
||||||
branch bits;
|
branch bits;
|
||||||
}
|
}
|
||||||
@@ -138,34 +136,29 @@ fn upkr_length(context_index: i32) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn upkr_bit(context_index: i32) -> i32 {
|
fn upkr_bit(context_index: i32) -> i32 {
|
||||||
let prob = (context_index * 4)!0x3c000;
|
let lazy prob = context_index?0x3c000;
|
||||||
|
|
||||||
loop refill {
|
loop refill {
|
||||||
if upkr_state < 1<<16 {
|
if upkr_state < 1<<12 {
|
||||||
upkr_state = (upkr_state << 8) | upkr_src_ptr?0;
|
upkr_state = (upkr_state << 8) | upkr_src_ptr?0;
|
||||||
upkr_src_ptr = upkr_src_ptr + 1;
|
upkr_src_ptr = upkr_src_ptr + 1;
|
||||||
branch refill;
|
branch refill;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lazy state_low = upkr_state & 0xfff;
|
let lazy state_low = upkr_state & 0xff;
|
||||||
let bit = state_low < prob;
|
let lazy state_hi = upkr_state >> 8;
|
||||||
|
let lazy bit = state_low < prob;
|
||||||
|
|
||||||
if bit {
|
upkr_state = state_low + select(bit, prob * state_hi, (0x100 - prob) * state_hi - prob);
|
||||||
upkr_state = prob * (upkr_state >> 12) + state_low;
|
|
||||||
prob = prob + ((0x1000 - prob) >> 4);
|
|
||||||
} else {
|
|
||||||
upkr_state = (0x1000 - prob) * (upkr_state >> 12) + state_low - prob;
|
|
||||||
prob = prob - (prob >> 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
(context_index * 4)!0x3c000 = prob;
|
context_index?0x3c000 = prob + ((7 + bit * 257 - prob) >> 4);
|
||||||
|
|
||||||
bit
|
bit
|
||||||
}
|
}
|
||||||
|
|
||||||
start fn unpack_base() {
|
start fn unpack_base() {
|
||||||
base_end = uncompress(0, 0x3c800);
|
base_end = uncompress(0, 0x3c200);
|
||||||
}
|
}
|
||||||
|
|
||||||
data 0 {
|
data 0 {
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ fn main() -> Result<()> {
|
|||||||
convert_font()?;
|
convert_font()?;
|
||||||
|
|
||||||
println!("Compiling loader module");
|
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)?;
|
File::create("bin/loader.wasm")?.write_all(&loader)?;
|
||||||
|
|
||||||
println!("Loader (including base module): {} bytes", loader.len());
|
println!("Loader (including base module): {} bytes", loader.len());
|
||||||
|
|
||||||
println!("Compiling platform module");
|
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");
|
println!("Compressing platform module");
|
||||||
let platform = uw8_tool::pack(
|
let platform = uw8_tool::pack(
|
||||||
&platform,
|
&platform,
|
||||||
uw8_tool::PackConfig::default().with_compression_level(4),
|
&uw8_tool::PackConfig::default().with_compression_level(4),
|
||||||
)?;
|
)?;
|
||||||
File::create("bin/platform.uw8")?.write_all(&platform)?;
|
File::create("bin/platform.uw8")?.write_all(&platform)?;
|
||||||
println!("Platform module: {} bytes", platform.len());
|
println!("Platform module: {} bytes", platform.len());
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import "env.memory" memory(4);
|
import "env.memory" memory(4);
|
||||||
|
|
||||||
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
import "env.cos" fn cos(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 {
|
export fn time() -> f32 {
|
||||||
(0!64) as f32 / 1000 as f32
|
(0!64) as f32 / 1000 as f32
|
||||||
@@ -29,11 +33,9 @@ export fn random() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn random64() -> i64 {
|
export fn random64() -> i64 {
|
||||||
let state: i64;
|
let lazy state = randomState ^ (randomState #>> 12i64);
|
||||||
randomState = (state := (
|
let lazy state = state ^ (state << 25i64);
|
||||||
state := randomState ^ (randomState #>> 12i64)
|
randomState = state ^ (state #>> 27i64);
|
||||||
) ^ (state << 25i64)
|
|
||||||
) ^ (state #>> 27i64);
|
|
||||||
randomState * 0x2545f4914f6cdd1di64
|
randomState * 0x2545f4914f6cdd1di64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +61,8 @@ export fn cls(col: i32) {
|
|||||||
let i: i32;
|
let i: i32;
|
||||||
textCursorX = 0;
|
textCursorX = 0;
|
||||||
textCursorY = 0;
|
textCursorY = 0;
|
||||||
graphicsText = 0;
|
outputChannel = 0;
|
||||||
col = (col & 255) * 0x1010101;
|
memory.fill(120, col, 320*240);
|
||||||
loop pixels {
|
|
||||||
i!120 = col;
|
|
||||||
branch_if (i := i + 4) < 320*240: pixels;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn setPixel(x: i32, y: i32, col: i32) {
|
export fn setPixel(x: i32, y: i32, col: i32) {
|
||||||
@@ -88,12 +86,70 @@ fn clamp(v: i32, min: i32, max: i32) -> i32 {
|
|||||||
export fn hline(x1: i32, x2: i32, y: i32, col: i32) {
|
export fn hline(x1: i32, x2: i32, y: i32, col: i32) {
|
||||||
x1 = clamp(x1, 0, 320);
|
x1 = clamp(x1, 0, 320);
|
||||||
x2 = clamp(x2, 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 ptr = y * 320 + x1;
|
||||||
let end = ptr + x2 - 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;
|
ptr?120 = col;
|
||||||
branch_if (ptr := ptr + 1) < end: pixels;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +171,26 @@ 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) {
|
||||||
|
let xl = nearest(x) as i32;
|
||||||
|
let xr = nearest(x + w) as i32;
|
||||||
|
let yt = nearest(y) as i32;
|
||||||
|
let yb = nearest(y + h) as i32;
|
||||||
|
|
||||||
|
hline(xl, xr, yt, col);
|
||||||
|
if yt < yb {
|
||||||
|
hline(xl, xr, yb - 1, col);
|
||||||
|
|
||||||
|
loop y {
|
||||||
|
setPixel(xl, yt, col);
|
||||||
|
if xl < xr {
|
||||||
|
setPixel(xr - 1, yt, col);
|
||||||
|
}
|
||||||
|
branch_if (yt := yt + 1) < yb: y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
|
export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
|
||||||
let y = clamp(nearest(cy - radius) as i32, 0, 240);
|
let y = clamp(nearest(cy - radius) as i32, 0, 240);
|
||||||
let maxY = clamp(nearest(cy + radius) as i32, 0, 240);
|
let maxY = clamp(nearest(cy + radius) as i32, 0, 240);
|
||||||
@@ -136,6 +212,58 @@ export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export fn circle_outline(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);
|
||||||
|
|
||||||
|
loop lines {
|
||||||
|
let lazy dy = y as f32 - cy + 0.5;
|
||||||
|
let inline q = radius * radius - dy * dy;
|
||||||
|
let w = sqrt(max(0 as f32, q));
|
||||||
|
|
||||||
|
let xlp = nearest(cx - prev_w) as i32;
|
||||||
|
let xl = nearest(cx - w) as i32;
|
||||||
|
let xrp = nearest(cx + prev_w) as i32;
|
||||||
|
let xr = nearest(cx + w) as i32;
|
||||||
|
if w >= prev_w {
|
||||||
|
if xl < xlp {
|
||||||
|
hline(xl, xlp, y, col);
|
||||||
|
} else {
|
||||||
|
if xl < xr {
|
||||||
|
setPixel(xl, y, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if xr > xrp {
|
||||||
|
hline(xrp, xr, y, col);
|
||||||
|
} else {
|
||||||
|
if xl < xr {
|
||||||
|
setPixel(xr - 1, y, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if xl > xlp {
|
||||||
|
hline(xlp, xl, y - 1, col);
|
||||||
|
} else {
|
||||||
|
if xlp < xrp {
|
||||||
|
setPixel(xlp, y - 1, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if xr < xrp {
|
||||||
|
hline(xr, xrp, y - 1, col);
|
||||||
|
} else {
|
||||||
|
if xlp < xrp {
|
||||||
|
setPixel(xrp - 1, y - 1, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y = y + 1;
|
||||||
|
prev_w = w;
|
||||||
|
branch_if y <= maxY: lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||||
let swapTmp: f32;
|
let swapTmp: f32;
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
@@ -184,6 +312,11 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
p = y1;
|
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;
|
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
||||||
p = floor(p) + 0.5 - p;
|
p = floor(p) + 0.5 - p;
|
||||||
if max_axis < 0 as f32 {
|
if max_axis < 0 as f32 {
|
||||||
@@ -195,7 +328,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
dy = dy / max_axis;
|
dy = dy / max_axis;
|
||||||
|
|
||||||
let f = min(max_axis, max(0 as f32, p));
|
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 {
|
if !steps {
|
||||||
return;
|
return;
|
||||||
@@ -208,7 +341,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
|
|
||||||
loop pixels {
|
loop pixels {
|
||||||
if steps := steps - 1 {
|
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;
|
x1 = x1 + dx;
|
||||||
y1 = y1 + dy;
|
y1 = y1 + dy;
|
||||||
branch pixels;
|
branch pixels;
|
||||||
@@ -216,7 +349,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f = min(max_axis, p) - p;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
@@ -227,7 +360,7 @@ global mut textCursorX = 0;
|
|||||||
global mut textCursorY = 0;
|
global mut textCursorY = 0;
|
||||||
global mut textColor = 15;
|
global mut textColor = 15;
|
||||||
global mut bgColor = 0;
|
global mut bgColor = 0;
|
||||||
global mut graphicsText = 0;
|
global mut outputChannel = 0;
|
||||||
|
|
||||||
export fn printChar(char: i32) {
|
export fn printChar(char: i32) {
|
||||||
loop chars {
|
loop chars {
|
||||||
@@ -236,16 +369,61 @@ export fn printChar(char: i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global mut controlCodeLength = 0;
|
||||||
|
|
||||||
fn printSingleChar(char: i32) {
|
fn printSingleChar(char: i32) {
|
||||||
if char == 4 | char == 5 {
|
if outputChannel >= 2 & (char < 4 | char > 6) {
|
||||||
graphicsText = char == 5;
|
logChar(char);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == 10 | (!graphicsText & textCursorX >= 320) {
|
controlCodeLength?0x12d20 = char;
|
||||||
textCursorX = 0;
|
controlCodeLength = controlCodeLength + 1;
|
||||||
|
char = 0x12d20?0;
|
||||||
|
if char < 32 & controlCodeLength < char?0x12d00 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controlCodeLength = 0;
|
||||||
|
|
||||||
|
if char == 1 {
|
||||||
|
drawChar(0x12d20?1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 !outputChannel & textCursorX < 0 {
|
||||||
|
textCursorX = 320-8;
|
||||||
|
printSingleChar(11);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 9 {
|
||||||
|
if !outputChannel & textCursorX >= 320 {
|
||||||
|
printChar(0xd0a);
|
||||||
|
}
|
||||||
|
textCursorX = textCursorX + 8;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 10 {
|
||||||
textCursorY = textCursorY + 8;
|
textCursorY = textCursorY + 8;
|
||||||
if !graphicsText & textCursorY >= 240 {
|
if !outputChannel & textCursorY >= 240 {
|
||||||
textCursorY = 240 - 8;
|
textCursorY = 240 - 8;
|
||||||
let i: i32;
|
let i: i32;
|
||||||
loop scroll_copy {
|
loop scroll_copy {
|
||||||
@@ -257,11 +435,83 @@ fn printSingleChar(char: i32) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if char == 11 {
|
||||||
|
textCursorY = textCursorY - 8;
|
||||||
|
if !outputChannel & textCursorY < 0 {
|
||||||
|
textCursorY = 0;
|
||||||
|
let i = 320 * (240 - 8);
|
||||||
|
loop scroll_copy {
|
||||||
|
i!(116 + 320 * 8) = i!116;
|
||||||
|
branch_if (i := i - 4): scroll_copy;
|
||||||
|
}
|
||||||
|
rectangle(0 as f32, 0 as f32, 320 as f32, 8 as f32, bgColor);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 12 {
|
||||||
|
cls(bgColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 13 {
|
||||||
|
textCursorX = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 14 {
|
||||||
|
bgColor = 0x12d20?1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 15 {
|
||||||
|
textColor = 0x12d20?1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 24 {
|
||||||
|
let tmp = textColor;
|
||||||
|
textColor = bgColor;
|
||||||
|
bgColor = tmp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == 31 {
|
||||||
|
textCursorX = 0x12d20?1 * (8 - outputChannel * 6);
|
||||||
|
textCursorY = 0x12d20?2 * (8 - outputChannel * 7);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char < 31 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawChar(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
data(0x12d00) {
|
||||||
|
i8(
|
||||||
|
1, 2, 1, 1, // 0-3
|
||||||
|
1, 1, 1, 1, // 4-7
|
||||||
|
1, 1, 1, 1, // 8-11
|
||||||
|
1, 1, 2, 2, // 12-15,
|
||||||
|
1, 1, 1, 1, // 16-19,
|
||||||
|
1, 1, 1, 1, // 20-23,
|
||||||
|
1, 1, 1, 1, // 24-27,
|
||||||
|
1, 1, 1, 3 // 28-31
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawChar(char: i32) {
|
||||||
|
if !outputChannel & textCursorX >= 320 {
|
||||||
|
printChar(0xd0a);
|
||||||
|
}
|
||||||
|
|
||||||
let y: i32;
|
let y: i32;
|
||||||
loop rows {
|
loop rows {
|
||||||
let bits = (char * 8 + y)?0x13400;
|
let bits = (char * 8 + y)?0x13400;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
if graphicsText {
|
if outputChannel {
|
||||||
loop pixels {
|
loop pixels {
|
||||||
if (bits := bits << 1) & 256 {
|
if (bits := bits << 1) & 256 {
|
||||||
setPixel(textCursorX + x, textCursorY + y, textColor);
|
setPixel(textCursorX + x, textCursorY + y, textColor);
|
||||||
@@ -313,11 +563,37 @@ export fn setBackgroundColor(col: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn setCursorPosition(x: i32, y: 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;
|
textCursorX = x * scale;
|
||||||
textCursorY = y * 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 //
|
// SETUP //
|
||||||
///////////
|
///////////
|
||||||
@@ -358,10 +634,19 @@ start fn setup() {
|
|||||||
branch_if (i := i - 1) >= 0: expand_sweetie;
|
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);
|
cls(0);
|
||||||
randomSeed(random());
|
randomSeed(random());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data 0x12c78 {
|
||||||
|
i32(80)
|
||||||
|
}
|
||||||
|
|
||||||
data 0x13000+192*4 {
|
data 0x13000+192*4 {
|
||||||
i32(
|
i32(
|
||||||
0x2c1c1a,
|
0x2c1c1a,
|
||||||
|
|||||||
2
release/.gitignore
vendored
Normal file
2
release/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/binaries
|
||||||
|
/build
|
||||||
56
release/make-release
Executable file
56
release/make-release
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd $(dirname $0)
|
||||||
|
|
||||||
|
# build web runtime
|
||||||
|
pushd ../web
|
||||||
|
rm -rf .parcel-cache
|
||||||
|
yarn parcel build src/index.html
|
||||||
|
popd
|
||||||
|
|
||||||
|
rm -rf build
|
||||||
|
mkdir -p build/microw8-linux
|
||||||
|
mkdir build/microw8-macos
|
||||||
|
mkdir build/microw8-windows
|
||||||
|
|
||||||
|
# unzip binaries build by github actions
|
||||||
|
pushd binaries
|
||||||
|
unzip -o uw8-linux.zip
|
||||||
|
chmod +x uw8
|
||||||
|
mv uw8 ../build/microw8-linux
|
||||||
|
unzip -o uw8-macos.zip
|
||||||
|
chmod +x uw8
|
||||||
|
mv uw8 ../build/microw8-macos
|
||||||
|
unzip -o uw8-windows.zip
|
||||||
|
mv uw8.exe ../build/microw8-windows
|
||||||
|
popd
|
||||||
|
|
||||||
|
for dir in build/*; do
|
||||||
|
mkdir $dir/examples
|
||||||
|
for example in ../examples/curlywas/*.cwa; do
|
||||||
|
cp $example $dir/examples
|
||||||
|
done
|
||||||
|
for example in ../examples/wat/*.wat; 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
|
||||||
|
done
|
||||||
|
|
||||||
|
cp ../web/dist/index.html $dir/microw8.html
|
||||||
|
cp ../README.md $dir
|
||||||
|
cp ../UNLICENSE $dir
|
||||||
|
done
|
||||||
|
|
||||||
|
VERSION=$(build/microw8-linux/uw8 version)
|
||||||
|
|
||||||
|
cd build
|
||||||
|
tar czf microw8-$VERSION-linux.tgz microw8-linux
|
||||||
|
tar czf microw8-$VERSION-macos.tgz microw8-macos
|
||||||
|
zip -r -9 microw8-$VERSION-windows.zip microw8-windows
|
||||||
0
site/.SRCINFO
Normal file
0
site/.SRCINFO
Normal file
@@ -18,7 +18,7 @@ highlight_theme = "ascetic-white"
|
|||||||
[extra]
|
[extra]
|
||||||
# Put all your custom variables here
|
# Put all your custom variables here
|
||||||
juice_logo_name = "MicroW8"
|
juice_logo_name = "MicroW8"
|
||||||
juice_logo_path = "microw8.svg"
|
juice_logo_path = "img/microw8.svg"
|
||||||
juice_extra_menu = [
|
juice_extra_menu = [
|
||||||
{ title = "Github", link = "https://github.com/exoticorn/microw8" }
|
{ title = "Github", link = "https://github.com/exoticorn/microw8" }
|
||||||
]
|
]
|
||||||
@@ -1,125 +1,81 @@
|
|||||||
+++
|
+++
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
MicroW8 is a WebAssembly based fantasy console inspired by the likes of [TIC-80](https://tic80.com/), [WASM-4](https://wasm4.org/) and [PICO-8](https://www.lexaloffle.com/pico-8.php).
|
||||||
|
|
||||||
|
The initial motivation behind MicroW8 was to explore whether there was a way to make WebAssembly viable for size-coding. (Size coding being the art of creating tiny (often <= 256 bytes) graphical effects and games.) The available examples so far are all in this space, however, I very carefully made sure that all design decisions make sense from the point of view of bigger projects as well.
|
||||||
|
|
||||||
|
## Specs
|
||||||
|
|
||||||
|
* Screen: 320x240, 256 colors, 60Hz
|
||||||
|
* Modules: Up to 256KB (WASM)
|
||||||
|
* Memory: 256KB
|
||||||
|
* Gamepad input (D-Pad + 4 Buttons)
|
||||||
|
|
||||||
|
For detailed [documentation see here](docs).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
* [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound
|
||||||
|
* [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:
|
||||||
|
|
||||||
|
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
|
||||||
|
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
|
||||||
|
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.
|
||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
* [v0.1pre1](v0.1pre1)
|
### v0.2.2
|
||||||
* [v0.1pre2](v0.1pre2)
|
|
||||||
* [v0.1pre3](v0.1pre3)
|
|
||||||
* [v0.1pre4](v0.1pre4)
|
|
||||||
|
|
||||||
## Spec
|
* [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)
|
||||||
|
|
||||||
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export
|
Changes:
|
||||||
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).
|
* 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`
|
||||||
|
|
||||||
Other imports provided by the platform, also all in module `env`:
|
### v0.2.1
|
||||||
|
|
||||||
* `fn acos(f32) -> f32`
|
* [Web runtime](v0.2.1)
|
||||||
* `fn asin(f32) -> f32`
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-linux.tgz)
|
||||||
* `fn atan(f32) -> f32`
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-macos.tgz)
|
||||||
* `fn atan2(f32, f32) -> f32`
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-windows.zip)
|
||||||
* `fn cos(f32) -> f32`
|
|
||||||
* `fn exp(f32, f32) -> f32`
|
|
||||||
* `fn log(f32) -> f32`
|
|
||||||
* `fn sin(f32) -> f32`
|
|
||||||
* `fn tan(f32) -> f32`
|
|
||||||
* `fn pow(f32) -> f32`
|
|
||||||
* `fn fmod(f32, f32) -> f32`
|
|
||||||
|
|
||||||
* `fn random() -> i32`
|
Changes:
|
||||||
* `fn randomf() -> f32`
|
|
||||||
* `fn randomSeed(i32)`
|
|
||||||
|
|
||||||
* `fn cls(color: i32)`
|
* new gpu accelerated renderer with (optional) crt filter
|
||||||
* `fn setPixel(x: i32, y: i32, color: i32)`
|
* optimized `hline` function, a big speed-up when drawing large filled circles or rectangles
|
||||||
* `fn getPixel(x: i32, y: i32) -> i32`
|
* print fractional size of packed `uw8` cart
|
||||||
* `fn hline(left: i32, right: i32, y: i32, color: i32)`
|
|
||||||
* `fn rectangle(x1: f32, y1: f32, x2: f32, y2: f32, color: i32)`
|
|
||||||
* `fn circle(cx: f32, cy: f32, radius: f32, color: i32)`
|
|
||||||
* `fn line(x1: f32, y1: f32, x2: f32, y2: f32, color: i32)`
|
|
||||||
|
|
||||||
* `fn time() -> f32`
|
### v0.2.0
|
||||||
* `fn isButtonPressed(btn: i32) -> i32`
|
|
||||||
* `fn isButtonTriggered(btn: i32) -> i32`
|
|
||||||
|
|
||||||
* `fn printChar(char: i32)`
|
* [Web runtime](v0.2.0)
|
||||||
* `fn printString(ptr: i32)`
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
|
||||||
* `fn printInt(num: i32)`
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
|
||||||
* `fn setTextColor(color: i32)`
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
|
||||||
* `fn setBackgroundColor(color: i32)`
|
|
||||||
* `fn setCursorPosition(x: i32, y: i32)`
|
|
||||||
|
|
||||||
### Memory map
|
Changes:
|
||||||
|
|
||||||
```
|
* [add sound support!](docs#sound)
|
||||||
00000-00040: user memory
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
00040-00044: time since module start in ms
|
* update curlywas:
|
||||||
00044-0004c: gamepad state
|
* add support for `else if`
|
||||||
0004c-00078: reserved
|
* add support for escape sequences in strings
|
||||||
00078-12c78: frame buffer
|
* add support for char literals
|
||||||
12c78-13000: reserved
|
* add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||||
13000-13400: palette
|
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||||
13400-13c00: font
|
|
||||||
13c00-14000: reserved
|
|
||||||
14000-40000: user memory
|
|
||||||
```
|
|
||||||
|
|
||||||
## `.uw8` format
|
### Older versions
|
||||||
|
|
||||||
The first byte of the file specifies the format version:
|
[Find older versions here.](versions)
|
||||||
|
|
||||||
#### Format version `00`:
|
|
||||||
|
|
||||||
This file is simply a standard WebAssembly module
|
|
||||||
|
|
||||||
#### Format version `01`:
|
|
||||||
|
|
||||||
The rest of this file is the same as a WebAssembly
|
|
||||||
module with the 8 byte header removed. This module
|
|
||||||
can leave out sections which are then taken from
|
|
||||||
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
|
|
||||||
`f32` parameters always preceed the `i32` parameters.
|
|
||||||
Then it includes all imports that MicroW8 provides,
|
|
||||||
a function section with a single function of type
|
|
||||||
`() -> void` and an export section that exports
|
|
||||||
the first function in the file under the name `upd`.
|
|
||||||
|
|
||||||
#### Format version `02`:
|
|
||||||
|
|
||||||
Same as version `01` except everything after the first byte is compressed
|
|
||||||
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
|
|
||||||
|
|
||||||
## Tooling
|
|
||||||
|
|
||||||
The [Web Assembly Binary Toolkit](https://github.com/WebAssembly/wabt) includes
|
|
||||||
a few useful tools, eg. `wat2wasm` to compile the WebAssemby text format to binary
|
|
||||||
wasm and `wasm2wat` to disassemble wasm binaries.
|
|
||||||
|
|
||||||
If you don't like the look of the `.wat` text format, you might want to take a
|
|
||||||
look at [CurlyWas](https://github.com/exoticorn/curlywas), a curly-braces infix
|
|
||||||
syntax for WebAssembly.
|
|
||||||
|
|
||||||
Once you have a size-efficient `.wasm` file, you can use [uw8-tool](https://github.com/exoticorn/microw8/tree/master/uw8-tool)
|
|
||||||
(currently included in the MicroW8 repository) to strip off sections of the
|
|
||||||
WebAssembly module that are provided by the MicroW8 platform.
|
|
||||||
|
|
||||||
Writing code for MicroW8 in C, Rust, AssemblyScript etc. should absolutely
|
|
||||||
possible but no examples are provided, yet.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
* [Skip Ahead](v0.1pre4#AiP94km33q5hBFbBcKl2W1ZMY67wHByUz15/qRHIr71Dvq8bTVux/DXZzSTKU5MufRXxnGEn6mOul4dil/5WLT1Y+Y/4TGd1E3U4rQaxlpcKObxc5YLdr4xo6jyBND96a569LoECv6Q4EIqxJi+3QPE21r/++js41XsBLr44X/O2XpP3kHQva3B8o6duD5WkLCf7PhsxjTBeND1ADIaJe05JCrBim18RPY9VYFmltqQ9gVzbxE/ZpojR/GJ4aCbx0hRn73RPhpA7Cd4jk0AVSOeRx+7kuHrLzpaeqeYWK5pYd/4Rv+8=) (230 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
|
|
||||||
* [OhNoAnotherTunnel](v0.1pre4#Ag95rdCB5Ww5NofyQaKF4P1mrNRso4azgiem4hK99Gh8OMzSpFq3NsNDo7O7pqln10D11l9uXr/ritw7OEzKwbEfCdvaRnS2Z0Kz0iDEZt/gIqOdvFmxsL1MjPQ4XInPbUJpQUonhQq29oP2omFabnQxn0bzoK7mZjcwc5GetHG+hGajkJcRr8oOnjfCol8RD+ha33GYtPnut+GLe4ktzf5UxZwGs6oT9qqC61lRDakN) (177 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
|
||||||
* [Technotunnel](v0.1pre4#AqL8HeK1M9dn2nWNIF5vaq/Vh64pMt5nJIFoFKpBMPUsGtDtpqjo1JbT9LzPhAxCqJ7Yh4TA6oTGd4xhLowf+cWZMY73+7AZmfXJJsBi4cej/hH+4wlAgxFIrnOYnr/18IpnZbsHf0eGm1BhahX74+cVR0TRmNQmYC7GhCNS3mv/3MJn74lCj7t28aBJPjEZhP9fGXdG2u5Egh/Tjdg=) (158 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
|
||||||
* [Font & Palette](v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe): Just a simple viewer for the default font and palette.
|
|
||||||
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
|
|
||||||
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
|
|
||||||
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.
|
|
||||||
614
site/content/docs.md
Normal file
614
site/content/docs.md
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
+++
|
||||||
|
title = "Docs"
|
||||||
|
description = "Docs"
|
||||||
|
+++
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export
|
||||||
|
a function `fn upd()` which will be called once per frame.
|
||||||
|
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-00050: reserved
|
||||||
|
00050-00070: sound data (synced to sound thread)
|
||||||
|
00070-00078: reserved
|
||||||
|
00078-12c78: frame buffer
|
||||||
|
12c78-12c7c: sound registers/work area base address (for sndGes function)
|
||||||
|
12c7c-13000: reserved
|
||||||
|
13000-13400: palette
|
||||||
|
13400-13c00: font
|
||||||
|
13c00-14000: reserved
|
||||||
|
14000-40000: user memory
|
||||||
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
All API functions are found in the `env` module.
|
||||||
|
|
||||||
|
## Math
|
||||||
|
|
||||||
|
These all do what you'd expect them to. All angles are in radians.
|
||||||
|
|
||||||
|
### fn asin(x: f32) -> f32
|
||||||
|
|
||||||
|
Returns the arcsine of `x`.
|
||||||
|
|
||||||
|
### fn acos(x: f32) -> f32
|
||||||
|
|
||||||
|
Returns the arccosine of `x`.
|
||||||
|
|
||||||
|
### fn atan(f32) -> f32
|
||||||
|
|
||||||
|
Returns the arctangent of `x`.
|
||||||
|
|
||||||
|
### fn atan2(y: f32, x: f32) -> f32
|
||||||
|
|
||||||
|
Returns the angle between the point `(x, y)` and the positive x-axis.
|
||||||
|
|
||||||
|
### fn sin(angle: f32) -> f32
|
||||||
|
|
||||||
|
Returns the sine of `angle`.
|
||||||
|
|
||||||
|
### fn tan(angle: f32) -> f32
|
||||||
|
|
||||||
|
Returns the tangent of `angle`.
|
||||||
|
|
||||||
|
### fn cos(angle: f32) -> f32
|
||||||
|
|
||||||
|
Returns the cosine of `angle`.
|
||||||
|
|
||||||
|
### fn exp(x: f32) -> f32
|
||||||
|
|
||||||
|
Returns `e^x`.
|
||||||
|
|
||||||
|
### fn log(x: f32) -> f32
|
||||||
|
|
||||||
|
Returns the natural logarithmus of `x`. Ie. `e^log(x) == x`.
|
||||||
|
|
||||||
|
### fn pow(x: f32, y: f32) -> f32
|
||||||
|
|
||||||
|
Returns `x^y`.
|
||||||
|
|
||||||
|
### fn fmod(x: f32, y: f32) -> f32
|
||||||
|
|
||||||
|
Returns `x` modulo `y`, ie. `x - floor(x / y) * y`. This means the sign of the result of `fmod` is the same as `y`.
|
||||||
|
|
||||||
|
## Random
|
||||||
|
|
||||||
|
MicroW8 provides a pretty good PRNG, namely xorshift64*. It is initialized to a constant seed at each startup, so if you
|
||||||
|
want to vary the random sequence you'll need to provide a seed yourself.
|
||||||
|
|
||||||
|
### fn random() -> i32
|
||||||
|
|
||||||
|
Returns a (pseudo-)random 32bit integer.
|
||||||
|
|
||||||
|
### fn randomf() -> f32
|
||||||
|
|
||||||
|
Returns a (pseudo-)random float equally distributed in `[0,1)`.
|
||||||
|
|
||||||
|
### fn randomSeed(seed: i32)
|
||||||
|
|
||||||
|
Seeds the PRNG with the given seed. The seed function is reasonably strong so that you can use
|
||||||
|
|
||||||
|
```
|
||||||
|
randomSeed(index);
|
||||||
|
random()
|
||||||
|
```
|
||||||
|
|
||||||
|
as a cheap random-access PRNG (aka noise function).
|
||||||
|
|
||||||
|
## Graphics
|
||||||
|
|
||||||
|
The default palette can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ). (Press Z on the keyboard to switch to palette.)
|
||||||
|
|
||||||
|
The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.
|
||||||
|
|
||||||
|
The drawing functions are sub-pixel accurate where applicable (line, circle). Pixel centers lie halfway between integer
|
||||||
|
coordinates. Ie. the top-left pixel covers the area `0,0 - 1,1`, with `0.5,0.5` being the pixel center.
|
||||||
|
|
||||||
|
### fn cls(color: i32)
|
||||||
|
|
||||||
|
Clears the screen to the given color index. Also sets the text cursor to `0, 0` and disables graphical text mode.
|
||||||
|
|
||||||
|
### fn setPixel(x: i32, y: i32, color: i32)
|
||||||
|
|
||||||
|
Sets the pixel at `x, y` to the given color index.
|
||||||
|
|
||||||
|
### fn getPixel(x: i32, y: i32) -> i32
|
||||||
|
|
||||||
|
Returns the color index at `x, y`. Returns `0` if the given coordinates are outside the screen.
|
||||||
|
|
||||||
|
### fn hline(left: i32, right: i32, y: i32, color: i32)
|
||||||
|
|
||||||
|
Fills the horizontal line `[left, right), y` with the given color index.
|
||||||
|
|
||||||
|
### fn rectangle(x: f32, y: f32, w: f32, h: f32, color: i32)
|
||||||
|
|
||||||
|
Fills the rectangle `x,y - x+w,y+h` with the given color index.
|
||||||
|
|
||||||
|
(Sets all pixels where the pixel center lies inside the rectangle.)
|
||||||
|
|
||||||
|
### fn circle(cx: f32, cy: f32, radius: f32, color: i32)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
Draws a one pixel outline on the inside of the given circle.
|
||||||
|
|
||||||
|
(Draws the outermost pixels that are still inside the circle area.)
|
||||||
|
|
||||||
|
### fn line(x1: f32, y1: f32, x2: f32, y2: f32, color: i32)
|
||||||
|
|
||||||
|
Draws a line from `x1,y1` to `x2,y2` in the given color index.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.
|
||||||
|
|
||||||
|
The buttons are numbered
|
||||||
|
|
||||||
|
| Button | Keyboard | Index |
|
||||||
|
| ------ | ----------- | ----- |
|
||||||
|
| Up | Arrow-Up | 0 |
|
||||||
|
| Down | Arrow-Down | 1 |
|
||||||
|
| Left | Arrow-Left | 2 |
|
||||||
|
| Right | Arrow-Right | 3 |
|
||||||
|
| A | Z | 4 |
|
||||||
|
| B | X | 5 |
|
||||||
|
| X | A | 6 |
|
||||||
|
| Y | S | 7 |
|
||||||
|
|
||||||
|
In addition to using the API functions below, the gamepad state can also be read as a bitfield of
|
||||||
|
pressed buttons at address 0x44. 0x48 holds the buttons that were pressed last frame.
|
||||||
|
|
||||||
|
### fn isButtonPressed(btn: i32) -> i32
|
||||||
|
|
||||||
|
Returns whether the buttons with the given index is pressed this frame.
|
||||||
|
|
||||||
|
### fn isButtonTriggered(btn: i32) -> i32
|
||||||
|
|
||||||
|
Returns whether the given button is newly pressed this frame.
|
||||||
|
|
||||||
|
### fn time() -> f32
|
||||||
|
|
||||||
|
Returns the time in seconds since the start of the cart.
|
||||||
|
|
||||||
|
The integer time in milliseconds can also be read at address 0x40.
|
||||||
|
|
||||||
|
## Text output
|
||||||
|
|
||||||
|
The default font can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ).
|
||||||
|
|
||||||
|
The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.
|
||||||
|
|
||||||
|
All text printing is done at the cursor position, which is advanced after printing each character.
|
||||||
|
The cursor is not visible.
|
||||||
|
|
||||||
|
Text printing can operate in two modes - normal and graphics. After startup and after `cls()` normal mode is active.
|
||||||
|
|
||||||
|
### Normal mode
|
||||||
|
|
||||||
|
In normal mode, text printing is constrained to an 8x8 character grid. Setting the cursor position to `2,3` will start printing at pixel coordinates `16,24`.
|
||||||
|
|
||||||
|
When printing characters, the full 8x8 pixels are painted with the text and background colors according to the character graphics in the font.
|
||||||
|
|
||||||
|
When moving/printing past the left or right border the cursor will automatically wrap to the previous/next line. When moving/printing past the upper/lower border, the screen will be scrolled down/up 8 pixels, filling the fresh line with the background color.
|
||||||
|
|
||||||
|
### Graphics mode
|
||||||
|
|
||||||
|
In graphics mode, text can be printed to any pixel position, the cursor position is set in pixel coordinates.
|
||||||
|
|
||||||
|
When printing characters only the foreground pixels are set, the background is "transparent".
|
||||||
|
|
||||||
|
Moving/printing past any border does not cause any special operation, the cursor just goes off-screen.
|
||||||
|
|
||||||
|
### 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, reset cursor to 0,0 |
|
||||||
|
| 5 | - | Switch to graphics mode |
|
||||||
|
| 6 | - | Switch output to (debug) console |
|
||||||
|
| 7 | - | Bell / trigger sound channel 0 |
|
||||||
|
| 8 | - | Move cursor left |
|
||||||
|
| 9 | - | Move cursor right |
|
||||||
|
| 10 | - | Move cursor down |
|
||||||
|
| 11 | - | Move cursor up |
|
||||||
|
| 12 | - | do `cls(background_color)` |
|
||||||
|
| 13 | - | Move cursor to the left border |
|
||||||
|
| 14 | color | Set the background color |
|
||||||
|
| 15 | color | Set the text color |
|
||||||
|
| 16-23 | - | Reserved |
|
||||||
|
| 24 | - | Swap text/background colors |
|
||||||
|
| 25-30 | - | Reserved |
|
||||||
|
| 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 implementation 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.
|
||||||
|
|
||||||
|
### fn printString(ptr: i32)
|
||||||
|
|
||||||
|
Prints the zero-terminated string at the given memory address.
|
||||||
|
|
||||||
|
### fn printInt(num: i32)
|
||||||
|
|
||||||
|
Prints `num` as a signed decimal number.
|
||||||
|
|
||||||
|
### fn setTextColor(color: i32)
|
||||||
|
|
||||||
|
Sets the text color.
|
||||||
|
|
||||||
|
### fn setBackgroundColor(color: i32)
|
||||||
|
|
||||||
|
Sets the background color.
|
||||||
|
|
||||||
|
### fn setCursorPosition(x: i32, y: i32)
|
||||||
|
|
||||||
|
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
|
||||||
|
wat or CurlyWas you don't need anything apart from `uw8` and a text editor of your choice.
|
||||||
|
|
||||||
|
## `uw8 run`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 run [<options>] <file>`
|
||||||
|
|
||||||
|
Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (WebAssembly text format) source file or a [CurlyWas](https://github.com/exoticorn/curlywas) source file.
|
||||||
|
|
||||||
|
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.
|
||||||
|
* `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size.
|
||||||
|
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
||||||
|
* `-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:
|
||||||
|
|
||||||
|
`uw8 pack [<options>] <infile> <outfile>`
|
||||||
|
|
||||||
|
Packs the WebAssembly module or text file, or [CurlyWas](https://github.com/exoticorn/curlywas) source file into a `.uw8` cart.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
|
||||||
|
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
|
||||||
|
|
||||||
|
## `uw8 unpack`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 unpack <infile> <outfile>`
|
||||||
|
|
||||||
|
Unpacks a MicroW8 module into a standard WebAssembly module.
|
||||||
|
|
||||||
|
## `uw8 compile`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 compile [<options>] <infile> <outfile>`
|
||||||
|
|
||||||
|
Compiles a [CurlyWas](https://github.com/exoticorn/curlywas) source file to a standard WebAssembly module. Most useful together with
|
||||||
|
the `--debug` option to get a module that works well in the Chrome debugger.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
* `-d`, `--debug`: Generate a name section to help debugging
|
||||||
|
|
||||||
|
## `uw8 filter-exports`
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
`uw8 filter-exports <infile> <outfile>`
|
||||||
|
|
||||||
|
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to `outfile`.
|
||||||
|
|
||||||
|
When compiling C code (or Rust, zig or others) to WebAssembly, you end up with a few exported global variables that are used for managing the heap and C stack, even if the code doesn't actually use those features. You can use this command to automatically remove them and gain a few bytes. See the C, Rust and zig examples in the MicroW8 repository.
|
||||||
|
|
||||||
|
# Other useful tools
|
||||||
|
|
||||||
|
The [Web Assembly Binary Toolkit](https://github.com/WebAssembly/wabt) includes
|
||||||
|
a few useful tools, eg. `wat2wasm` to compile the WebAssemby text format to binary
|
||||||
|
wasm and `wasm2wat` to disassemble wasm binaries.
|
||||||
|
|
||||||
|
[Binaryen](https://github.com/WebAssembly/binaryen) includes `wasm-opt` which enable additional optimizations over what LLVM (the backend that is used by most compilers that target WebAssembly) can do.
|
||||||
|
|
||||||
|
# Distribution
|
||||||
|
|
||||||
|
The classical distribution option is just to put the `.uw8` cart into a zip file, let people run it themselves, either in the `uw8` tool or in the web runtime.
|
||||||
|
|
||||||
|
If you want to go this way, you might consider including `microw8.html` in your download. It's specifically designed to be a small (~10KB at the moment), self-contained HTML file for just this reason. That way, anyone who has downloaded you production can run it, even when offline, provided they have a modern web browser at hand. Also, should future versions of MicroW8 ever introduce any kind of incompatibilities, they'd still have a compatible version right there without hunting arround for an old version.
|
||||||
|
|
||||||
|
## Base64 encoded link
|
||||||
|
|
||||||
|
For small productions (<= 1024 bytes), when you load them in the web runtime, the URL is automatically updated to include the cart as base64 encoded data. You can just give that URL to others for them to run your prod.
|
||||||
|
|
||||||
|
## url parameter
|
||||||
|
|
||||||
|
Another option is to put the cart on a webserver and add `#url=url/to/the/cart.uw8` to the end of the web runtime URL. ([Like this](../v0.1pre5#url=../uw8/skipahead.uw8))
|
||||||
|
|
||||||
|
If the cart and the web runtime are on different domains, you'll have to make sure that [CORS header](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers) are enabled for the cart, otherwise the web runtime won't be able to load it.
|
||||||
|
|
||||||
|
Feel free to put the web runtime on your own server if it makes sense to you, its [license](https://unlicense.org/) allows you to do anything you want with it.
|
||||||
|
|
||||||
|
## `.html` + `.uw8`
|
||||||
|
|
||||||
|
At startup the web runtime will try to load a cart in the same directory as the `.html` file. If the URL of the web runtime ends in `.html` it will try to load a cart with the same name and the extension `.uw8`. If the URL of the web runtime ends in a `/` it will try to load a `cart.uw8` at that location.
|
||||||
|
|
||||||
|
So, you could for example serve the web runtime as `https://example.org/mytunnel.html` and the cart as `https://example.org/mytunnel.uw8` and send people to the HTML page to run the cart. Or you could put them up as `https://example.org/mytunnel/index.html` and `https://example.org/mytunnel/cart.uw8` and send people to `https://example.org/mytunnel`.
|
||||||
|
|
||||||
|
If a cart is found and loaded in this way, the load button is hidden.
|
||||||
|
|
||||||
|
## Itch.io
|
||||||
|
|
||||||
|
The above `.html` + `.uw8` option works great on [Itch.io](https://itch.io) as well. Put these two files into a zip archive:
|
||||||
|
|
||||||
|
* `index.html`: a copy of the web runtime (`microw8.html` in the MicroW8 download)
|
||||||
|
* `index.uw8`: Your game cart
|
||||||
|
|
||||||
|
Upload the zip file to itch.io and make sure to set the embedded viewport size to exactly (!) 640x480 pixel. At that exact size the web runtime hides everything except for the MicroW8 screen.
|
||||||
|
|
||||||
|
If instead you actually *want* to display the border around the screen and the byte size you can try a size of about 720x620.
|
||||||
|
|
||||||
|
[See here for an example upload.](https://exoticorn.itch.io/skipahead)
|
||||||
|
|
||||||
|
# `.uw8` format
|
||||||
|
|
||||||
|
The first byte of the file specifies the format version:
|
||||||
|
|
||||||
|
## Format version `00`:
|
||||||
|
|
||||||
|
This file is simply a standard WebAssembly module
|
||||||
|
|
||||||
|
## Format version `01`:
|
||||||
|
|
||||||
|
The rest of this file is the same as a WebAssembly
|
||||||
|
module with the 8 byte header removed. This module
|
||||||
|
can leave out sections which are then taken from
|
||||||
|
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
|
||||||
|
`f32` parameters always preceed the `i32` parameters.
|
||||||
|
Then it includes all imports that MicroW8 provides,
|
||||||
|
a function section with a single function of type
|
||||||
|
`() -> void` and an export section that exports
|
||||||
|
the first function in the file under the name `upd`.
|
||||||
|
|
||||||
|
## Format version `02`:
|
||||||
|
|
||||||
|
Same as version `01` except everything after the first byte is compressed
|
||||||
|
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
|
||||||
|
|
||||||
|
# The web runtime
|
||||||
|
|
||||||
|
Load carts into the web runtime either by using the "Load cart..." button, or by dragging the file
|
||||||
|
onto the screen area.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
For input, you can either use a standard gamepad or keyboard. On a keyboard use the arrow keys and the keys Z, X, A and S to emulate the A, B, X and Y buttons.
|
||||||
|
|
||||||
|
## Video recording
|
||||||
|
|
||||||
|
Press F10 to start recording, press again to stop. Then a download dialog will open for the video file.
|
||||||
|
The file might miss some metadata needed to load in some video editing tools, in that case you can run
|
||||||
|
it through ffmpeg like this `ffmpeg -i IN_NAME.webm -c copy -o OUT_NAME.webm to fix it up.
|
||||||
|
|
||||||
|
To convert it to 1280x720, for example for a lovebyte upload, you can use:
|
||||||
|
|
||||||
|
```
|
||||||
|
ffmpeg -i IN.webm -vf "scale=960:720:flags=neighbor,pad=1280:720:160:0" -r 60 OUT.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|
Pressing F9 opens a download dialog with a screenshot.
|
||||||
|
|
||||||
|
## Devkit mode
|
||||||
|
|
||||||
|
Append `#devkit` to the web runtime url in order to switch to devkit mode. In devkit mode, standard web assembly modules
|
||||||
|
are loaded bypassing the loader, removing all size restrictions. At the same time, the memory limit is increased to 1GB.
|
||||||
105
site/content/versions.md
Normal file
105
site/content/versions.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
+++
|
||||||
|
description = "Versions"
|
||||||
|
+++
|
||||||
|
|
||||||
|
### 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)
|
||||||
5
site/static/img/microw8.svg
Normal file
5
site/static/img/microw8.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg width="120" height="120" version="1.1" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m3.9727 6.3613v27.945h7.8691l2.8281 66.549 6.9199-56.922h16.969l6.4375 56.922 4.1523-66.549h17.148l-9.5488 81.697h27.195c-4.1759-2.1972-7.3595-5.016-9.5234-8.4707-2.2464-3.6905-3.3691-7.6805-3.3691-11.973 0-4.7735 1.3845-8.9251 4.1523-12.455 2.7679-3.53 6.4981-6.3194 11.191-8.3652-4.2521-2.7277-7.3402-5.4948-9.2656-8.3027-1.8854-2.8481-2.8281-6.5783-2.8281-11.191 0-4.7334 1.223-8.8256 3.6699-12.275 2.4469-3.4498 5.7559-6.0981 9.9277-7.9434 4.212-1.8854 8.9647-2.8262 14.26-2.8262 5.2203 0 9.8397 0.84602 13.867 2.5234v-28.363h-40.227a14.855 14.855 0 0 1 0.019531 0.40234 14.855 14.855 0 0 1-14.855 14.855 14.855 14.855 0 0 1-14.855-14.855 14.855 14.855 0 0 1 0.052734-0.40234zm98.186 38.775c-3.0888 0-5.4939 0.863-7.2187 2.5879-1.6848 1.6848-2.5273 4.192-2.5273 7.5215 0 2.9283 0.98169 5.3538 2.9473 7.2793 1.9656 1.8854 5.0352 3.6113 9.207 5.1758 2.7277-2.0057 4.5928-3.971 5.5957-5.8965 1.0028-1.9656 1.5039-4.1129 1.5039-6.4395 0-2.9684-0.8017-5.4144-2.4062-7.3398-1.5644-1.9255-3.9326-2.8887-7.1016-2.8887zm-72.203 12.938-6.4902 57.93h12.393zm68.113 21.781c-2.4469 1.5644-4.3938 3.5502-5.8379 5.957-1.4441 2.4068-2.166 5.2741-2.166 8.6035 0 3.4498 1.0041 6.2781 3.0098 8.4844 2.0458 2.1662 5.0726 3.25 9.084 3.25 4.1317 0 7.142-1.1043 9.0274-3.3106 1.8854-2.2464 2.8281-4.8723 2.8281-7.8809 0-2.7679-0.56235-5.0153-1.6856-6.7402-1.0831-1.7249-2.7886-3.2096-5.1152-4.4531-2.3266-1.2836-5.3738-2.5864-9.1445-3.9102z" fill="#85a6b2" fill-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
site/static/img/technotunnel.png
Normal file
BIN
site/static/img/technotunnel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
1
site/static/v0.1.0/index.html
Normal file
1
site/static/v0.1.0/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.1.1/index.html
Normal file
1
site/static/v0.1.1/index.html
Normal file
File diff suppressed because one or more lines are too long
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.1pre5/index.html
Normal file
1
site/static/v0.1pre5/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
@@ -2,12 +2,16 @@
|
|||||||
{% block hero %}
|
{% block hero %}
|
||||||
<div>
|
<div>
|
||||||
<section>
|
<section>
|
||||||
<h1 class="text-center heading-text">A WebAssembly based sizecoding platform</h1>
|
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
||||||
</section>
|
</section>
|
||||||
<a href="v0.1pre4">
|
<a href="v0.2.2">
|
||||||
<div class="demonstration-gif" style="width:640px;height:512px;background-color:black"></div>
|
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="explore-more text"
|
||||||
|
onclick="document.getElementById('features').scrollIntoView({behavior: 'smooth'})">
|
||||||
|
Explore More ⇩
|
||||||
|
</div>
|
||||||
{% endblock hero %}
|
{% endblock hero %}
|
||||||
|
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
|
|||||||
62
src/filewatcher.rs
Normal file
62
src/filewatcher.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
|
||||||
|
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc};
|
||||||
|
|
||||||
|
pub struct FileWatcher {
|
||||||
|
watcher: RecommendedWatcher,
|
||||||
|
watched_files: BTreeSet<PathBuf>,
|
||||||
|
directories: BTreeSet<PathBuf>,
|
||||||
|
rx: mpsc::Receiver<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileWatcher {
|
||||||
|
pub fn new() -> Result<FileWatcher> {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let watcher = notify::recommended_watcher(move |res| match res {
|
||||||
|
Ok(event) => _ = tx.send(event),
|
||||||
|
Err(err) => eprintln!("Error watching for file changes: {err}"),
|
||||||
|
})?;
|
||||||
|
Ok(FileWatcher {
|
||||||
|
watcher,
|
||||||
|
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.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>> {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(event) => match event.kind {
|
||||||
|
EventKind::Create(_) | EventKind::Modify(_) => {
|
||||||
|
for path in event.paths {
|
||||||
|
let handle = same_file::Handle::from_path(&path)?;
|
||||||
|
for file in &self.watched_files {
|
||||||
|
if handle == same_file::Handle::from_path(file)? {
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
202
src/lib.rs
202
src/lib.rs
@@ -1,191 +1,19 @@
|
|||||||
use std::io::prelude::*;
|
mod filewatcher;
|
||||||
use std::path::Path;
|
#[cfg(feature = "native")]
|
||||||
use std::{fs::File, time::Instant};
|
mod run_native;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
mod run_web;
|
||||||
|
|
||||||
|
pub use filewatcher::FileWatcher;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
pub use run_native::MicroW8;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
pub use run_web::RunWebServer;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
|
||||||
use wasmtime::{
|
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
|
||||||
};
|
|
||||||
|
|
||||||
static GAMEPAD_KEYS: &'static [Key] = &[Key::Up, Key::Down, Key::Left, Key::Right, Key::Z, Key::X, Key::A, Key::S];
|
pub trait Runtime {
|
||||||
|
fn is_open(&self) -> bool;
|
||||||
pub struct MicroW8 {
|
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||||
engine: Engine,
|
fn run_frame(&mut self) -> Result<()>;
|
||||||
loader_module: Module,
|
|
||||||
window: Window,
|
|
||||||
window_buffer: Vec<u32>,
|
|
||||||
instance: Option<UW8Instance>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UW8Instance {
|
|
||||||
store: Store<()>,
|
|
||||||
memory: Memory,
|
|
||||||
end_frame: TypedFunc<(), ()>,
|
|
||||||
update: TypedFunc<(), ()>,
|
|
||||||
start_time: Instant,
|
|
||||||
module: Vec<u8>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MicroW8 {
|
|
||||||
pub fn new() -> Result<MicroW8> {
|
|
||||||
let engine = wasmtime::Engine::default();
|
|
||||||
|
|
||||||
let loader_module =
|
|
||||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
|
||||||
|
|
||||||
let mut options = WindowOptions::default();
|
|
||||||
options.scale = minifb::Scale::X2;
|
|
||||||
options.scale_mode = minifb::ScaleMode::AspectRatioStretch;
|
|
||||||
options.resize = true;
|
|
||||||
let mut window = Window::new("MicroW8", 320, 240, options)?;
|
|
||||||
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
|
|
||||||
|
|
||||||
Ok(MicroW8 {
|
|
||||||
engine,
|
|
||||||
loader_module,
|
|
||||||
window,
|
|
||||||
window_buffer: vec![0u32; 320 * 240],
|
|
||||||
instance: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
|
||||||
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) {
|
|
||||||
self.instance = None;
|
|
||||||
for v in &mut self.window_buffer {
|
|
||||||
*v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
let mut module = vec![];
|
|
||||||
File::open(path)?.read_to_end(&mut module)?;
|
|
||||||
self.load_from_memory(&module)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_memory(&mut self, module_data: &[u8]) -> Result<()> {
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
|
||||||
|
|
||||||
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.clone())?;
|
|
||||||
|
|
||||||
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 platform_data = include_bytes!("../platform/bin/platform.uw8");
|
|
||||||
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
|
||||||
let platform_length =
|
|
||||||
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
|
|
||||||
let platform_module =
|
|
||||||
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
|
|
||||||
|
|
||||||
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
|
|
||||||
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])?;
|
|
||||||
|
|
||||||
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())?;
|
|
||||||
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
|
||||||
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
|
||||||
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
|
||||||
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
|
||||||
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 {
|
|
||||||
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
|
||||||
}
|
|
||||||
for i in 0..16 {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
&format!("g_reserved{}", i),
|
|
||||||
wasmtime::Global::new(
|
|
||||||
&mut store,
|
|
||||||
GlobalType::new(ValType::I32, Mutability::Const),
|
|
||||||
0.into(),
|
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
|
|
||||||
|
|
||||||
for export in platform_instance.exports(&mut store) {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
export.name(),
|
|
||||||
export
|
|
||||||
.into_func()
|
|
||||||
.expect("platform surely only exports functions"),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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")?;
|
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
|
||||||
store,
|
|
||||||
memory,
|
|
||||||
end_frame,
|
|
||||||
update,
|
|
||||||
start_time: Instant::now(),
|
|
||||||
module: module_data.into()
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_frame(&mut self) -> Result<()> {
|
|
||||||
if let Some(mut instance) = self.instance.take() {
|
|
||||||
{
|
|
||||||
let time = instance.start_time.elapsed().as_millis() as i32;
|
|
||||||
let mut gamepad: u32 = 0;
|
|
||||||
for key in self.window.get_keys().unwrap_or(Vec::new()) {
|
|
||||||
if let Some(index) = GAMEPAD_KEYS.iter().enumerate().find(|(_, &k)| k == key).map(|(i, _)| i) {
|
|
||||||
gamepad |= 1 << index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.update.call(&mut instance.store, ())?;
|
|
||||||
instance.end_frame.call(&mut instance.store, ())?;
|
|
||||||
|
|
||||||
let memory = instance.memory.data(&instance.store);
|
|
||||||
let framebuffer = &memory[120..];
|
|
||||||
let palette = &memory[0x13000..];
|
|
||||||
for i in 0..320 * 240 {
|
|
||||||
let offset = framebuffer[i] as usize * 4;
|
|
||||||
self.window_buffer[i] = 0xff000000
|
|
||||||
| ((palette[offset + 0] 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_from_memory(&instance.module)?;
|
|
||||||
} else {
|
|
||||||
self.instance = Some(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.window
|
|
||||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
320
src/main.rs
320
src/main.rs
@@ -1,46 +1,73 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use notify::{DebouncedEvent, Watcher};
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
#[cfg(feature = "native")]
|
||||||
use uw8::MicroW8;
|
use uw8::MicroW8;
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
use uw8::RunWebServer;
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
|
use uw8::Runtime;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default()).init();
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
match args.subcommand()?.as_ref().map(|s| s.as_str()) {
|
// 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"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
Some("run") => run(args),
|
Some("run") => run(args),
|
||||||
|
Some("pack") => pack(args),
|
||||||
|
Some("unpack") => unpack(args),
|
||||||
|
Some("compile") => compile(args),
|
||||||
|
Some("filter-exports") => filter_exports(args),
|
||||||
|
Some("help") | None => {
|
||||||
|
println!("uw8 {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
println!();
|
||||||
|
println!("Usage:");
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
|
println!(" uw8 run [-t/--timeout <frames>] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
|
||||||
|
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
|
||||||
|
println!(" uw8 unpack <in-file> <out-file>");
|
||||||
|
println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
|
||||||
|
println!(" uw8 filter-exports <in-wasm> <out-wasm>");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
eprintln!("Unknown command '{}'", other);
|
eprintln!("Unknown command '{}'", other);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
println!("Usage:");
|
|
||||||
println!(" uw8 run [-w] [-p] [-c] [-l] [-o <out-file>] <file>");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
fn run(mut args: Arguments) -> Result<()> {
|
fn run(mut args: Arguments) -> Result<()> {
|
||||||
let watch_mode = args.contains(["-w", "--watch"]);
|
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();
|
let mut config = Config::default();
|
||||||
config.pack = args.contains(["-p", "--pack"]);
|
if args.contains(["-p", "--pack"]) {
|
||||||
if args.contains(["-c", "--compress"]) {
|
let mut pack = uw8_tool::PackConfig::default();
|
||||||
config.compression = Some(2);
|
if args.contains(["-u", "--uncompressed"]) {
|
||||||
}
|
pack = pack.uncompressed();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(level) = args.opt_value_from_str(["-l", "--level"])? {
|
if let Some(level) = args.opt_value_from_str(["-l", "--level"])? {
|
||||||
config.compression = Some(level);
|
pack = pack.with_compression_level(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.pack = Some(pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) =
|
if let Some(path) =
|
||||||
@@ -49,36 +76,72 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
config.output_path = Some(path);
|
config.output_path = Some(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
let run_browser = args.contains(["-b", "--browser"]);
|
||||||
|
#[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 filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut uw8 = MicroW8::new()?;
|
let mut watcher = uw8::FileWatcher::new()?;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
use std::process::exit;
|
||||||
let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
|
||||||
|
|
||||||
if watch_mode {
|
let mut runtime: Box<dyn Runtime> = if !run_browser {
|
||||||
watcher.watch(&filename, notify::RecursiveMode::NonRecursive)?;
|
#[cfg(not(feature = "native"))]
|
||||||
}
|
unimplemented!();
|
||||||
|
#[cfg(feature = "native")]
|
||||||
if let Err(err) = load_cart(&filename, &mut uw8, &config) {
|
{
|
||||||
eprintln!("Load error: {}", err);
|
let mut microw8 = MicroW8::new(timeout, window_config)?;
|
||||||
if !watch_mode {
|
if disable_audio {
|
||||||
exit(1);
|
microw8.disable_audio();
|
||||||
|
}
|
||||||
|
Box::new(microw8)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
#[cfg(not(feature = "browser"))]
|
||||||
|
unimplemented!();
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
Box::new(RunWebServer::new())
|
||||||
|
};
|
||||||
|
|
||||||
while uw8.is_open() {
|
let mut first_run = true;
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(DebouncedEvent::Create(_) | DebouncedEvent::Write(_)) => {
|
while runtime.is_open() {
|
||||||
if let Err(err) = load_cart(&filename, &mut uw8, &config) {
|
if first_run || watcher.poll_changed_file()?.is_some() {
|
||||||
eprintln!("Load error: {}", err);
|
let (result, dependencies) = start_cart(&filename, &mut *runtime, &config);
|
||||||
|
if watch_mode {
|
||||||
|
for dep in dependencies {
|
||||||
|
watcher.add_file(dep)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
if let Err(err) = result {
|
||||||
_ => (),
|
eprintln!("Load error: {}", err);
|
||||||
|
if !watch_mode {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first_run = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uw8.run_frame()?;
|
if let Err(err) = runtime.run_frame() {
|
||||||
|
eprintln!("Runtime error: {}", err);
|
||||||
|
if !watch_mode {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -86,41 +149,166 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Config {
|
struct Config {
|
||||||
pack: bool,
|
pack: Option<uw8_tool::PackConfig>,
|
||||||
compression: Option<u8>,
|
|
||||||
output_path: Option<PathBuf>,
|
output_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cart(filename: &Path, uw8: &mut MicroW8, config: &Config) -> Result<()> {
|
fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>) {
|
||||||
let mut cart = vec![];
|
let mut dependencies = Vec::new();
|
||||||
File::open(filename)?.read_to_end(&mut cart)?;
|
fn inner(filename: &Path, config: &Config, dependencies: &mut Vec<PathBuf>) -> Result<Vec<u8>> {
|
||||||
|
let mut cart = match SourceType::of_file(filename)? {
|
||||||
if cart[0] >= 10 {
|
SourceType::Binary => {
|
||||||
let src = String::from_utf8(cart)?;
|
let mut cart = vec![];
|
||||||
cart = if src.chars().find(|c| !c.is_whitespace()) == Some('(') {
|
File::open(filename)?.read_to_end(&mut cart)?;
|
||||||
wat::parse_str(src)?
|
cart
|
||||||
} else {
|
}
|
||||||
curlywas::compile_str(&src, filename, curlywas::Options::default())?
|
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 config.pack {
|
if let Some(ref pack_config) = config.pack {
|
||||||
let mut pack_config = uw8_tool::PackConfig::default();
|
cart = uw8_tool::pack(&cart, pack_config)?;
|
||||||
if let Some(level) = config.compression {
|
println!(
|
||||||
pack_config = pack_config.with_compression_level(level);
|
"\npacked size: {} bytes ({:.2})",
|
||||||
|
cart.len(),
|
||||||
|
uw8_tool::compressed_size(&cart)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
cart = uw8_tool::pack(&cart, pack_config)?;
|
|
||||||
println!("packed size: {} bytes", cart.len());
|
if let Some(ref path) = config.output_path {
|
||||||
|
File::create(path)?.write_all(&cart)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cart)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref path) = config.output_path {
|
let result = inner(filename, config, &mut dependencies);
|
||||||
File::create(path)?.write_all(&cart)?;
|
|
||||||
|
if dependencies.is_empty() {
|
||||||
|
dependencies.push(filename.to_path_buf());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = uw8.load_from_memory(&cart) {
|
(result, dependencies)
|
||||||
eprintln!("Load error: {}", err);
|
}
|
||||||
Err(err)
|
|
||||||
} else {
|
enum SourceType {
|
||||||
Ok(())
|
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<()>, 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), dependencies)
|
||||||
|
} else {
|
||||||
|
(Ok(()), dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack(mut args: Arguments) -> Result<()> {
|
||||||
|
let mut pack_config = uw8_tool::PackConfig::default();
|
||||||
|
|
||||||
|
if args.contains(["-u", "--uncompressed"]) {
|
||||||
|
pack_config = pack_config.uncompressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(level) = args.opt_value_from_str(["-l", "--level"])? {
|
||||||
|
pack_config = pack_config.with_compression_level(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cart = load_cart(
|
||||||
|
&in_file,
|
||||||
|
&Config {
|
||||||
|
pack: Some(pack_config),
|
||||||
|
output_path: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.0?;
|
||||||
|
|
||||||
|
File::create(out_file)?.write_all(&cart)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack(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()))?;
|
||||||
|
|
||||||
|
uw8_tool::unpack_file(&in_file, &out_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(mut args: Arguments) -> Result<()> {
|
||||||
|
let mut options = curlywas::Options::default();
|
||||||
|
if args.contains(["-d", "--debug"]) {
|
||||||
|
options = options.with_debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
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).0?;
|
||||||
|
File::create(out_file)?.write_all(&module)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_exports(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()))?;
|
||||||
|
|
||||||
|
uw8_tool::filter_exports(&in_file, &out_file)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
1
src/run-web.html
Normal file
1
src/run-web.html
Normal file
File diff suppressed because one or more lines are too long
481
src/run_native.rs
Normal file
481
src/run_native.rs
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{thread, time::Instant};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use cpal::traits::*;
|
||||||
|
use rubato::Resampler;
|
||||||
|
use uw8_window::{Window, WindowConfig};
|
||||||
|
use wasmtime::{
|
||||||
|
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct MicroW8 {
|
||||||
|
window: Window,
|
||||||
|
stream: Option<cpal::Stream>,
|
||||||
|
engine: Engine,
|
||||||
|
loader_module: Module,
|
||||||
|
disable_audio: bool,
|
||||||
|
module_data: Option<Vec<u8>>,
|
||||||
|
timeout: u32,
|
||||||
|
instance: Option<UW8Instance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UW8Instance {
|
||||||
|
store: Store<()>,
|
||||||
|
memory: Memory,
|
||||||
|
end_frame: TypedFunc<(), ()>,
|
||||||
|
update: Option<TypedFunc<(), ()>>,
|
||||||
|
start_time: Instant,
|
||||||
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
|
sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for UW8Instance {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Ok(mut watchdog) = self.watchdog.lock() {
|
||||||
|
watchdog.stop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UW8WatchDog {
|
||||||
|
engine: Engine,
|
||||||
|
stop: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MicroW8 {
|
||||||
|
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 window = Window::new(window_config)?;
|
||||||
|
|
||||||
|
Ok(MicroW8 {
|
||||||
|
window,
|
||||||
|
stream: None,
|
||||||
|
engine,
|
||||||
|
loader_module,
|
||||||
|
disable_audio: false,
|
||||||
|
module_data: None,
|
||||||
|
timeout: timeout.unwrap_or(0),
|
||||||
|
instance: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_audio(&mut self) {
|
||||||
|
self.disable_audio = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Runtime for MicroW8 {
|
||||||
|
fn is_open(&self) -> bool {
|
||||||
|
self.window.is_open()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
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 platform_data = include_bytes!("../platform/bin/platform.uw8");
|
||||||
|
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
||||||
|
let platform_length =
|
||||||
|
load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize;
|
||||||
|
let platform_module =
|
||||||
|
wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?;
|
||||||
|
|
||||||
|
memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data);
|
||||||
|
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(),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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())?;
|
||||||
|
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
||||||
|
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
||||||
|
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
||||||
|
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
||||||
|
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 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,
|
||||||
|
GlobalType::new(ValType::I32, Mutability::Const),
|
||||||
|
0.into(),
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
for export in platform_instance.exports(&mut *store) {
|
||||||
|
linker.define(
|
||||||
|
"env",
|
||||||
|
export.name(),
|
||||||
|
export
|
||||||
|
.into_func()
|
||||||
|
.expect("platform surely only exports functions"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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("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.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
configs.sort_by_key(|config| {
|
||||||
|
let rate = 44100
|
||||||
|
.max(config.min_sample_rate().0)
|
||||||
|
.min(config.max_sample_rate().0);
|
||||||
|
if rate >= 44100 {
|
||||||
|
rate - 44100
|
||||||
|
} else {
|
||||||
|
(44100 - rate) * 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let config = configs
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("Could not find float output config"))?;
|
||||||
|
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 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 stream = device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
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 mem = memory.data_mut(&mut store);
|
||||||
|
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
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(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||||
|
resampler.input_buffers[1]
|
||||||
|
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
|
||||||
|
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 {
|
||||||
|
for v in buffer {
|
||||||
|
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
||||||
|
sample_index = sample_index.wrapping_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outer_buffer = &mut outer_buffer[step_size..];
|
||||||
|
current_time =
|
||||||
|
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
dbg!(err);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Uw8Sound { stream, tx })
|
||||||
|
}
|
||||||
97
src/run_web.rs
Normal file
97
src/run_web.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
use tokio_stream::{wrappers::BroadcastStream, Stream, StreamExt};
|
||||||
|
use warp::{http::Response, Filter};
|
||||||
|
|
||||||
|
pub struct RunWebServer {
|
||||||
|
cart: Arc<Mutex<Vec<u8>>>,
|
||||||
|
tx: broadcast::Sender<()>,
|
||||||
|
socket_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunWebServer {
|
||||||
|
pub fn new() -> 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()
|
||||||
|
.enable_time()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create tokio runtime");
|
||||||
|
rt.block_on(async {
|
||||||
|
let html = warp::path::end().map(|| {
|
||||||
|
Response::builder()
|
||||||
|
.header("Content-Type", "text/html")
|
||||||
|
.body(include_str!("run-web.html"))
|
||||||
|
});
|
||||||
|
|
||||||
|
let cart = warp::path("cart")
|
||||||
|
.map(move || server_cart.lock().map_or(Vec::new(), |c| c.clone()));
|
||||||
|
|
||||||
|
let events = warp::path("events").and(warp::get()).map(move || {
|
||||||
|
fn event_stream(
|
||||||
|
tx: &broadcast::Sender<()>,
|
||||||
|
) -> impl Stream<Item = Result<warp::sse::Event, std::convert::Infallible>>
|
||||||
|
{
|
||||||
|
BroadcastStream::new(tx.subscribe())
|
||||||
|
.map(|_| Ok(warp::sse::Event::default().data("L")))
|
||||||
|
}
|
||||||
|
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
||||||
|
});
|
||||||
|
|
||||||
|
let server_future = warp::serve(html.or(cart).or(events)).bind(server_addr);
|
||||||
|
server_future.await
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
let _ignore_result = self.tx.send(());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_open(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_frame(&mut self) -> Result<()> {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RunWebServer {
|
||||||
|
fn default() -> RunWebServer {
|
||||||
|
RunWebServer::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
30
test/ges_test.cwa
Normal file
30
test/ges_test.cwa
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||||
|
|
||||||
|
include "../platform/src/ges.cwa"
|
||||||
|
|
||||||
|
export fn snd(t: i32) -> f32 {
|
||||||
|
gesSnd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
80?0 = 32!32 / 200 & 2 | 0x41;
|
||||||
|
80?3 = (32!32 / 400)%7*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)
|
||||||
|
}
|
||||||
244
uw8-tool/Cargo.lock
generated
244
uw8-tool/Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.45"
|
version = "1.0.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
@@ -56,6 +56,56 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
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 = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -69,10 +119,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "lexopt"
|
||||||
version = "0.2.108"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.112"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
@@ -96,10 +161,34 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "percent-encoding"
|
||||||
version = "0.4.2"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sacabase"
|
name = "sacabase"
|
||||||
@@ -110,6 +199,37 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@@ -122,14 +242,67 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upkr"
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/exoticorn/upkr.git?rev=7d280bd#7d280bd533b037d579b6cdf12fb69046eb5c2c91"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.8.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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "upkr"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cdivsufsort",
|
"cdivsufsort",
|
||||||
"pbr",
|
"lexopt",
|
||||||
"pico-args",
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -140,8 +313,35 @@ dependencies = [
|
|||||||
"pbr",
|
"pbr",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"upkr",
|
"upkr",
|
||||||
|
"walrus",
|
||||||
"wasm-encoder",
|
"wasm-encoder",
|
||||||
"wasmparser",
|
"wasmparser 0.99.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walrus"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"leb128",
|
||||||
|
"log",
|
||||||
|
"walrus-macro",
|
||||||
|
"wasmparser 0.77.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -152,18 +352,28 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.8.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.81.0"
|
version = "0.77.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.99.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmparser = "0.81"
|
wasmparser = "0.99"
|
||||||
wasm-encoder = "0.8"
|
wasm-encoder = "0.22"
|
||||||
|
walrus = "0.19"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
pico-args = "0.4"
|
pico-args = "0.5"
|
||||||
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "7d280bd" }
|
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "080db40d0088bbee2bdf3c5c75288ac7853d6b7a" }
|
||||||
pbr = "1"
|
pbr = "1"
|
||||||
@@ -3,7 +3,7 @@ use std::{collections::HashMap, fs::File, path::Path};
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use wasm_encoder::{
|
use wasm_encoder::{
|
||||||
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection,
|
CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, ImportSection,
|
||||||
Instruction, MemoryType, Module, TypeSection, ValType,
|
Instruction, MemoryType, Module, TypeSection, ValType,
|
||||||
};
|
};
|
||||||
use ValType::*;
|
use ValType::*;
|
||||||
@@ -71,23 +71,103 @@ impl BaseModule {
|
|||||||
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
|
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "cls", &[I32], None);
|
add_function(&mut functions, &type_map, "cls", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setPixel", &[I32, I32, I32], None);
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "getPixel", &[I32, I32], Some(I32));
|
&mut functions,
|
||||||
add_function(&mut functions, &type_map, "hline", &[I32, I32, I32, I32], None);
|
&type_map,
|
||||||
add_function(&mut functions, &type_map, "rectangle", &[F32, F32, F32, F32, I32], None);
|
"setPixel",
|
||||||
add_function(&mut functions, &type_map, "circle", &[F32, F32, F32, I32], None);
|
&[I32, I32, I32],
|
||||||
add_function(&mut functions, &type_map, "line", &[F32, F32, F32, F32, I32], None);
|
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, "time", &[], Some(F32));
|
||||||
add_function(&mut functions, &type_map, "isButtonPressed", &[I32], Some(I32));
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "isButtonTriggered", &[I32], Some(I32));
|
&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, "printChar", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "printString", &[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, "printInt", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
|
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setBackgroundColor", &[I32], None);
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "setCursorPosition", &[I32, I32], None);
|
&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, "exp", &[F32], Some(F32));
|
||||||
|
|
||||||
|
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
||||||
|
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
|
||||||
|
|
||||||
for i in functions.len()..64 {
|
for i in functions.len()..64 {
|
||||||
add_function(
|
add_function(
|
||||||
@@ -138,13 +218,13 @@ impl BaseModule {
|
|||||||
let mut imports = ImportSection::new();
|
let mut imports = ImportSection::new();
|
||||||
|
|
||||||
for (module, name, type_) in &self.function_imports {
|
for (module, name, type_) in &self.function_imports {
|
||||||
imports.import(*module, Some(name.as_str()), EntityType::Function(*type_));
|
imports.import(*module, name.as_str(), EntityType::Function(*type_));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (module, name, import) in &self.global_imports {
|
for (module, name, import) in &self.global_imports {
|
||||||
imports.import(
|
imports.import(
|
||||||
*module,
|
*module,
|
||||||
Some(name.as_str()),
|
name.as_str(),
|
||||||
EntityType::Global(wasm_encoder::GlobalType {
|
EntityType::Global(wasm_encoder::GlobalType {
|
||||||
val_type: import.type_,
|
val_type: import.type_,
|
||||||
mutable: import.mutable,
|
mutable: import.mutable,
|
||||||
@@ -154,11 +234,12 @@ impl BaseModule {
|
|||||||
|
|
||||||
imports.import(
|
imports.import(
|
||||||
"env",
|
"env",
|
||||||
Some("memory"),
|
"memory",
|
||||||
MemoryType {
|
MemoryType {
|
||||||
minimum: self.memory as u64,
|
minimum: self.memory as u64,
|
||||||
maximum: None,
|
maximum: None,
|
||||||
memory64: false,
|
memory64: false,
|
||||||
|
shared: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -179,7 +260,7 @@ impl BaseModule {
|
|||||||
let mut exports = ExportSection::new();
|
let mut exports = ExportSection::new();
|
||||||
|
|
||||||
for (name, fnc) in &self.exports {
|
for (name, fnc) in &self.exports {
|
||||||
exports.export(*name, Export::Function(*fnc));
|
exports.export(*name, ExportKind::Func, *fnc);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.section(&exports);
|
module.section(&exports);
|
||||||
@@ -207,10 +288,75 @@ impl BaseModule {
|
|||||||
|
|
||||||
pub fn create_binary(path: &Path) -> Result<()> {
|
pub fn create_binary(path: &Path) -> Result<()> {
|
||||||
let base1 = BaseModule::for_format_version(1)?.to_wasm();
|
let base1 = BaseModule::for_format_version(1)?.to_wasm();
|
||||||
let data = upkr::pack(&base1, 4, None);
|
let data = upkr::pack(&base1, 4, &upkr::Config::default(), None);
|
||||||
File::create(path)?.write_all(&data)?;
|
File::create(path)?.write_all(&data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
fn add_function(
|
||||||
@@ -238,3 +384,30 @@ fn lookup_type(
|
|||||||
};
|
};
|
||||||
*type_map.get(&key).unwrap()
|
*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),
|
||||||
|
];
|
||||||
|
|||||||
21
uw8-tool/src/filter_exports.rs
Normal file
21
uw8-tool/src/filter_exports.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
for id in exports_to_delete {
|
||||||
|
module.exports.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
walrus::passes::gc::run(&mut module);
|
||||||
|
|
||||||
|
module.emit_wasm_file(out_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
mod base_module;
|
mod base_module;
|
||||||
|
mod filter_exports;
|
||||||
mod pack;
|
mod pack;
|
||||||
|
|
||||||
pub use base_module::BaseModule;
|
pub use base_module::BaseModule;
|
||||||
|
pub use filter_exports::filter_exports;
|
||||||
pub use pack::{pack, pack_file, unpack, unpack_file, PackConfig};
|
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 std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use uw8_tool::BaseModule;
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
use uw8_tool::BaseModule;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
@@ -15,18 +15,31 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
"pack" => {
|
"pack" => {
|
||||||
let mut config = uw8_tool::PackConfig::default();
|
let mut config = uw8_tool::PackConfig::default();
|
||||||
if args.contains(["-c", "--compress"]) {
|
if args.contains(["-u", "--uncompressed"]) {
|
||||||
config = config.with_compression();
|
config = config.uncompressed();
|
||||||
}
|
}
|
||||||
let source: PathBuf = args.free_from_str()?;
|
let source: PathBuf = args.free_from_str()?;
|
||||||
let dest: PathBuf = args.free_from_str()?;
|
let dest: PathBuf = args.free_from_str()?;
|
||||||
uw8_tool::pack_file(&source, &dest, config)?;
|
uw8_tool::pack_file(&source, &dest, &config)?;
|
||||||
}
|
}
|
||||||
"unpack" => {
|
"unpack" => {
|
||||||
let source: PathBuf = args.free_from_str()?;
|
let source: PathBuf = args.free_from_str()?;
|
||||||
let dest: PathBuf = args.free_from_str()?;
|
let dest: PathBuf = args.free_from_str()?;
|
||||||
uw8_tool::unpack_file(&source, &dest)?;
|
uw8_tool::unpack_file(&source, &dest)?;
|
||||||
}
|
}
|
||||||
|
"filter-exports" => {
|
||||||
|
let source: PathBuf = args.free_from_str()?;
|
||||||
|
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);
|
eprintln!("Unknown subcommand '{}'", cmd);
|
||||||
print_help();
|
print_help();
|
||||||
@@ -44,6 +57,7 @@ fn print_help() {
|
|||||||
"Usage:
|
"Usage:
|
||||||
uw8-tool make-base <version>
|
uw8-tool make-base <version>
|
||||||
uw8-tool pack <wasm file> <uw8 file>
|
uw8-tool pack <wasm file> <uw8 file>
|
||||||
uw8-tool unpack <uw8 file> <wasm file>"
|
uw8-tool unpack <uw8 file> <wasm file>
|
||||||
|
uw8-tool filter-exports <wasm file> <wasm file>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
use wasm_encoder as enc;
|
use wasm_encoder as enc;
|
||||||
use wasmparser::{
|
use wasmparser::{
|
||||||
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
||||||
ImportSectionEntryType, ImportSectionReader, TypeSectionReader,
|
ImportSectionReader, TableSectionReader, TypeRef, TypeSectionReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PackConfig {
|
pub struct PackConfig {
|
||||||
@@ -18,8 +18,8 @@ pub struct PackConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PackConfig {
|
impl PackConfig {
|
||||||
pub fn with_compression(mut self) -> Self {
|
pub fn uncompressed(mut self) -> Self {
|
||||||
self.compression = Some(2);
|
self.compression = None;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,11 +31,13 @@ impl PackConfig {
|
|||||||
|
|
||||||
impl Default for PackConfig {
|
impl Default for PackConfig {
|
||||||
fn default() -> PackConfig {
|
fn default() -> PackConfig {
|
||||||
PackConfig { compression: None }
|
PackConfig {
|
||||||
|
compression: Some(2),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pack_file(source: &Path, dest: &Path, config: PackConfig) -> Result<()> {
|
pub fn pack_file(source: &Path, dest: &Path, config: &PackConfig) -> Result<()> {
|
||||||
let mut source_data = vec![];
|
let mut source_data = vec![];
|
||||||
File::open(source)?.read_to_end(&mut source_data)?;
|
File::open(source)?.read_to_end(&mut source_data)?;
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ pub fn pack_file(source: &Path, dest: &Path, config: PackConfig) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pack(data: &[u8], config: PackConfig) -> Result<Vec<u8>> {
|
pub fn pack(data: &[u8], config: &PackConfig) -> Result<Vec<u8>> {
|
||||||
let base = BaseModule::for_format_version(1)?;
|
let base = BaseModule::for_format_version(1)?;
|
||||||
|
|
||||||
let parsed_module = ParsedModule::parse(data)?;
|
let parsed_module = ParsedModule::parse(data)?;
|
||||||
@@ -61,11 +63,13 @@ pub fn pack(data: &[u8], config: PackConfig) -> Result<Vec<u8>> {
|
|||||||
uw8.extend_from_slice(&upkr::pack(
|
uw8.extend_from_slice(&upkr::pack(
|
||||||
&result[8..],
|
&result[8..],
|
||||||
level,
|
level,
|
||||||
|
&upkr::Config::default(),
|
||||||
Some(&mut |pos| {
|
Some(&mut |pos| {
|
||||||
pb.set(pos as u64);
|
pb.set(pos as u64);
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
pb.finish();
|
pb.finish();
|
||||||
|
std::io::stdout().flush()?;
|
||||||
Ok(uw8)
|
Ok(uw8)
|
||||||
} else {
|
} else {
|
||||||
let mut uw8 = vec![1];
|
let mut uw8 = vec![1];
|
||||||
@@ -86,7 +90,10 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
|
|||||||
let (version, data) = match data[0] {
|
let (version, data) = match data[0] {
|
||||||
0 => return Ok(data),
|
0 => return Ok(data),
|
||||||
1 => (1, data[1..].to_vec()),
|
1 => (1, data[1..].to_vec()),
|
||||||
2 => (1, upkr::unpack(&data[1..])),
|
2 => (
|
||||||
|
1,
|
||||||
|
upkr::unpack(&data[1..], &upkr::Config::default(), 4 * 1024 * 1024)?,
|
||||||
|
),
|
||||||
other => bail!("Uknown format version {}", other),
|
other => bail!("Uknown format version {}", other),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,8 +136,8 @@ pub fn unpack(data: Vec<u8>) -> Result<Vec<u8>> {
|
|||||||
Ok(dest)
|
Ok(dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_val_type(type_: &wasmparser::Type) -> Result<ValType> {
|
fn to_val_type(type_: &wasmparser::ValType) -> Result<ValType> {
|
||||||
use wasmparser::Type::*;
|
use wasmparser::ValType::*;
|
||||||
Ok(match *type_ {
|
Ok(match *type_ {
|
||||||
I32 => ValType::I32,
|
I32 => ValType::I32,
|
||||||
I64 => ValType::I64,
|
I64 => ValType::I64,
|
||||||
@@ -140,7 +147,7 @@ fn to_val_type(type_: &wasmparser::Type) -> Result<ValType> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_val_type_vec(types: &[wasmparser::Type]) -> Result<Vec<ValType>> {
|
fn to_val_type_vec(types: &[wasmparser::ValType]) -> Result<Vec<ValType>> {
|
||||||
types.into_iter().map(to_val_type).collect()
|
types.into_iter().map(to_val_type).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +162,8 @@ struct ParsedModule<'a> {
|
|||||||
start_section: Option<u32>,
|
start_section: Option<u32>,
|
||||||
function_bodies: Vec<wasmparser::FunctionBody<'a>>,
|
function_bodies: Vec<wasmparser::FunctionBody<'a>>,
|
||||||
data_section: Option<Section<()>>,
|
data_section: Option<Section<()>>,
|
||||||
|
table_section: Option<Section<()>>,
|
||||||
|
element_section: Option<Vec<Element>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParsedModule<'a> {
|
impl<'a> ParsedModule<'a> {
|
||||||
@@ -169,6 +178,8 @@ impl<'a> ParsedModule<'a> {
|
|||||||
let mut start_section = None;
|
let mut start_section = None;
|
||||||
let mut function_bodies = Vec::new();
|
let mut function_bodies = Vec::new();
|
||||||
let mut data_section = None;
|
let mut data_section = None;
|
||||||
|
let mut table_section = None;
|
||||||
|
let mut element_section = None;
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
@@ -194,7 +205,7 @@ impl<'a> ParsedModule<'a> {
|
|||||||
import_section = Some(Section::new(range, ImportSection::parse(reader)?));
|
import_section = Some(Section::new(range, ImportSection::parse(reader)?));
|
||||||
}
|
}
|
||||||
Payload::GlobalSection(reader) => {
|
Payload::GlobalSection(reader) => {
|
||||||
global_section = Some(Section::new(range, reader.get_count()));
|
global_section = Some(Section::new(range, reader.count()));
|
||||||
}
|
}
|
||||||
Payload::FunctionSection(reader) => {
|
Payload::FunctionSection(reader) => {
|
||||||
function_section = Some(Section::new(range, read_function_section(reader)?));
|
function_section = Some(Section::new(range, read_function_section(reader)?));
|
||||||
@@ -208,10 +219,26 @@ impl<'a> ParsedModule<'a> {
|
|||||||
Payload::DataSection(_) => {
|
Payload::DataSection(_) => {
|
||||||
data_section = Some(Section::new(range, ()));
|
data_section = Some(Section::new(range, ()));
|
||||||
}
|
}
|
||||||
|
Payload::TableSection(reader) => {
|
||||||
|
validate_table_section(reader)?;
|
||||||
|
table_section = Some(Section::new(range, ()));
|
||||||
|
}
|
||||||
|
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::CodeSectionStart { .. } => (),
|
||||||
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
||||||
Payload::CustomSection { .. } => (),
|
Payload::CustomSection { .. } => (),
|
||||||
Payload::End => break,
|
Payload::End(..) => break,
|
||||||
other => bail!("Unsupported section: {:?}", other),
|
other => bail!("Unsupported section: {:?}", other),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +255,8 @@ impl<'a> ParsedModule<'a> {
|
|||||||
start_section,
|
start_section,
|
||||||
function_bodies,
|
function_bodies,
|
||||||
data_section,
|
data_section,
|
||||||
|
table_section,
|
||||||
|
element_section,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,6 +433,10 @@ impl<'a> ParsedModule<'a> {
|
|||||||
module.section(&function_section);
|
module.section(&function_section);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(tables) = self.table_section {
|
||||||
|
copy_section(&mut module, &self.data[tables.range.clone()])?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref globals) = self.globals {
|
if let Some(ref globals) = self.globals {
|
||||||
copy_section(&mut module, &self.data[globals.range.clone()])?;
|
copy_section(&mut module, &self.data[globals.range.clone()])?;
|
||||||
for i in 0..globals.data {
|
for i in 0..globals.data {
|
||||||
@@ -433,7 +466,7 @@ impl<'a> ParsedModule<'a> {
|
|||||||
{
|
{
|
||||||
let mut export_section = enc::ExportSection::new();
|
let mut export_section = enc::ExportSection::new();
|
||||||
for (name, fnc) in my_exports {
|
for (name, fnc) in my_exports {
|
||||||
export_section.export(&name, enc::Export::Function(fnc));
|
export_section.export(&name, enc::ExportKind::Func, fnc);
|
||||||
}
|
}
|
||||||
module.section(&export_section);
|
module.section(&export_section);
|
||||||
}
|
}
|
||||||
@@ -445,6 +478,25 @@ impl<'a> ParsedModule<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(elements) = self.element_section {
|
||||||
|
let mut element_section = wasm_encoder::ElementSection::new();
|
||||||
|
for element in elements {
|
||||||
|
let mut functions = Vec::with_capacity(element.functions.len());
|
||||||
|
for index in element.functions {
|
||||||
|
functions.push(*function_map.get(&index).ok_or_else(|| {
|
||||||
|
anyhow!("Function index {} not found in function map", index)
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
element_section.active(
|
||||||
|
None,
|
||||||
|
&wasm_encoder::ConstExpr::i32_const(element.start_index as i32),
|
||||||
|
ValType::FuncRef,
|
||||||
|
wasm_encoder::Elements::Functions(&functions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
module.section(&element_section);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut code_section = enc::CodeSection::new();
|
let mut code_section = enc::CodeSection::new();
|
||||||
|
|
||||||
@@ -486,21 +538,33 @@ fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::Funct
|
|||||||
|
|
||||||
for type_def in reader {
|
for type_def in reader {
|
||||||
match type_def? {
|
match type_def? {
|
||||||
wasmparser::TypeDef::Func(fnc) => {
|
wasmparser::Type::Func(fnc) => {
|
||||||
if fnc.returns.len() > 1 {
|
if fnc.results().len() > 1 {
|
||||||
bail!("Multi-value not supported");
|
bail!("Multi-value not supported");
|
||||||
}
|
}
|
||||||
let params = to_val_type_vec(&fnc.params)?;
|
let params = to_val_type_vec(fnc.params())?;
|
||||||
let result = to_val_type_vec(&fnc.returns)?.into_iter().next();
|
let result = to_val_type_vec(fnc.results())?.into_iter().next();
|
||||||
function_types.push(FunctionType { params, result });
|
function_types.push(FunctionType { params, result });
|
||||||
}
|
}
|
||||||
t => bail!("Unsupported type def {:?}", t),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(function_types)
|
Ok(function_types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_table_section(reader: TableSectionReader) -> Result<()> {
|
||||||
|
if reader.count() != 1 {
|
||||||
|
bail!("Only up to one table supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_ = reader.into_iter().next().unwrap()?;
|
||||||
|
if type_.element_type != wasmparser::ValType::FuncRef {
|
||||||
|
bail!("Only one funcref table is supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Section<T> {
|
struct Section<T> {
|
||||||
range: std::ops::Range<usize>,
|
range: std::ops::Range<usize>,
|
||||||
@@ -528,45 +592,38 @@ impl ImportSection {
|
|||||||
|
|
||||||
for import in reader {
|
for import in reader {
|
||||||
let import = import?;
|
let import = import?;
|
||||||
if let Some(field) = import.field {
|
match import.ty {
|
||||||
match import.ty {
|
TypeRef::Func(type_) => {
|
||||||
ImportSectionEntryType::Function(type_) => {
|
functions.push(FunctionImport {
|
||||||
functions.push(FunctionImport {
|
module: import.module.to_string(),
|
||||||
module: import.module.to_string(),
|
field: import.name.to_string(),
|
||||||
field: field.to_string(),
|
type_,
|
||||||
type_,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
ImportSectionEntryType::Memory(mem) => {
|
|
||||||
if import.module != "env" || field != "memory" {
|
|
||||||
bail!(
|
|
||||||
"Wrong name of memory import {}.{}, should be env.memory",
|
|
||||||
import.module,
|
|
||||||
field
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if mem.memory64 || mem.shared {
|
|
||||||
bail!("Wrong memory import options: {:?}", import.ty);
|
|
||||||
}
|
|
||||||
memory = mem.maximum.unwrap_or(mem.initial) as u32;
|
|
||||||
}
|
|
||||||
ImportSectionEntryType::Global(glbl) => {
|
|
||||||
globals.push(GlobalImport {
|
|
||||||
module: import.module.to_string(),
|
|
||||||
field: field.to_string(),
|
|
||||||
type_: GlobalType {
|
|
||||||
type_: to_val_type(&glbl.content_type)?,
|
|
||||||
mutable: glbl.mutable,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => bail!("Unsupported import item {:?}", import.ty),
|
|
||||||
}
|
}
|
||||||
} else {
|
TypeRef::Memory(mem) => {
|
||||||
bail!(
|
if import.module != "env" || import.name != "memory" {
|
||||||
"Found import without field, only module '{}'",
|
bail!(
|
||||||
import.module
|
"Wrong name of memory import {}.{}, should be env.memory",
|
||||||
);
|
import.module,
|
||||||
|
import.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if mem.memory64 || mem.shared {
|
||||||
|
bail!("Wrong memory import options: {:?}", import.ty);
|
||||||
|
}
|
||||||
|
memory = mem.maximum.unwrap_or(mem.initial) as u32;
|
||||||
|
}
|
||||||
|
TypeRef::Global(glbl) => {
|
||||||
|
globals.push(GlobalImport {
|
||||||
|
module: import.module.to_string(),
|
||||||
|
field: import.name.to_string(),
|
||||||
|
type_: GlobalType {
|
||||||
|
type_: to_val_type(&glbl.content_type)?,
|
||||||
|
mutable: glbl.mutable,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => bail!("Unsupported import item {:?}", import.ty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,6 +635,48 @@ impl ImportSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Element {
|
||||||
|
start_index: u32,
|
||||||
|
functions: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element {
|
||||||
|
fn parse(element: wasmparser::Element) -> Result<Element> {
|
||||||
|
match element.items {
|
||||||
|
wasmparser::ElementItems::Functions(funcs_reader) => {
|
||||||
|
let start_index = if let wasmparser::ElementKind::Active {
|
||||||
|
offset_expr,
|
||||||
|
table_index: 0,
|
||||||
|
} = element.kind
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Unsupported table element kind");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut functions = Vec::with_capacity(funcs_reader.count() as usize);
|
||||||
|
for index in funcs_reader {
|
||||||
|
functions.push(index?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Element {
|
||||||
|
start_index,
|
||||||
|
functions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => bail!("Table element type is not FuncRef"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FunctionImport {
|
struct FunctionImport {
|
||||||
module: String,
|
module: String,
|
||||||
@@ -605,8 +704,8 @@ fn read_export_section(reader: ExportSectionReader) -> Result<Vec<(String, u32)>
|
|||||||
for export in reader {
|
for export in reader {
|
||||||
let export = export?;
|
let export = export?;
|
||||||
match export.kind {
|
match export.kind {
|
||||||
ExternalKind::Function => {
|
ExternalKind::Func => {
|
||||||
function_exports.push((export.field.to_string(), export.index));
|
function_exports.push((export.name.to_string(), export.index));
|
||||||
}
|
}
|
||||||
_ => (), // just ignore all other kinds since MicroW8 doesn't expect any exports other than functions
|
_ => (), // just ignore all other kinds since MicroW8 doesn't expect any exports other than functions
|
||||||
}
|
}
|
||||||
@@ -627,13 +726,11 @@ fn remap_function(
|
|||||||
}
|
}
|
||||||
let mut function = enc::Function::new(locals);
|
let mut function = enc::Function::new(locals);
|
||||||
|
|
||||||
let block_type = |ty: wasmparser::TypeOrFuncType| -> Result<enc::BlockType> {
|
let block_type = |ty: wasmparser::BlockType| -> Result<enc::BlockType> {
|
||||||
Ok(match ty {
|
Ok(match ty {
|
||||||
wasmparser::TypeOrFuncType::Type(wasmparser::Type::EmptyBlockType) => {
|
wasmparser::BlockType::Empty => enc::BlockType::Empty,
|
||||||
enc::BlockType::Empty
|
wasmparser::BlockType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
|
||||||
}
|
wasmparser::BlockType::FuncType(ty) => enc::BlockType::FunctionType(
|
||||||
wasmparser::TypeOrFuncType::Type(ty) => enc::BlockType::Result(to_val_type(&ty)?),
|
|
||||||
wasmparser::TypeOrFuncType::FuncType(ty) => enc::BlockType::FunctionType(
|
|
||||||
*type_map
|
*type_map
|
||||||
.get(&ty)
|
.get(&ty)
|
||||||
.ok_or_else(|| anyhow!("Function type index out of range: {}", ty))?,
|
.ok_or_else(|| anyhow!("Function type index out of range: {}", ty))?,
|
||||||
@@ -647,7 +744,7 @@ fn remap_function(
|
|||||||
.ok_or_else(|| anyhow!("Global index out of range: {}", idx))?)
|
.ok_or_else(|| anyhow!("Global index out of range: {}", idx))?)
|
||||||
};
|
};
|
||||||
|
|
||||||
fn mem(m: wasmparser::MemoryImmediate) -> enc::MemArg {
|
fn mem(m: wasmparser::MemArg) -> enc::MemArg {
|
||||||
enc::MemArg {
|
enc::MemArg {
|
||||||
offset: m.offset,
|
offset: m.offset,
|
||||||
align: m.align as u32,
|
align: m.align as u32,
|
||||||
@@ -662,9 +759,9 @@ fn remap_function(
|
|||||||
function.instruction(&match op? {
|
function.instruction(&match op? {
|
||||||
De::Unreachable => En::Unreachable,
|
De::Unreachable => En::Unreachable,
|
||||||
De::Nop => En::Nop,
|
De::Nop => En::Nop,
|
||||||
De::Block { ty } => En::Block(block_type(ty)?),
|
De::Block { blockty } => En::Block(block_type(blockty)?),
|
||||||
De::Loop { ty } => En::Loop(block_type(ty)?),
|
De::Loop { blockty } => En::Loop(block_type(blockty)?),
|
||||||
De::If { ty } => En::If(block_type(ty)?),
|
De::If { blockty } => En::If(block_type(blockty)?),
|
||||||
De::Else => En::Else,
|
De::Else => En::Else,
|
||||||
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
|
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
|
||||||
De::End => En::End,
|
De::End => En::End,
|
||||||
@@ -677,8 +774,17 @@ fn remap_function(
|
|||||||
.get(&function_index)
|
.get(&function_index)
|
||||||
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
||||||
),
|
),
|
||||||
De::CallIndirect { .. }
|
De::CallIndirect {
|
||||||
| De::ReturnCall { .. }
|
type_index,
|
||||||
|
table_index,
|
||||||
|
table_byte: _, // what is this supposed to be?
|
||||||
|
} => En::CallIndirect {
|
||||||
|
ty: *type_map
|
||||||
|
.get(&type_index)
|
||||||
|
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
|
||||||
|
table: table_index,
|
||||||
|
},
|
||||||
|
De::ReturnCall { .. }
|
||||||
| De::ReturnCallIndirect { .. }
|
| De::ReturnCallIndirect { .. }
|
||||||
| De::Delegate { .. }
|
| De::Delegate { .. }
|
||||||
| De::CatchAll => todo!(),
|
| De::CatchAll => todo!(),
|
||||||
@@ -694,16 +800,16 @@ fn remap_function(
|
|||||||
De::I64Load { memarg } => En::I64Load(mem(memarg)),
|
De::I64Load { memarg } => En::I64Load(mem(memarg)),
|
||||||
De::F32Load { memarg } => En::F32Load(mem(memarg)),
|
De::F32Load { memarg } => En::F32Load(mem(memarg)),
|
||||||
De::F64Load { memarg } => En::F64Load(mem(memarg)),
|
De::F64Load { memarg } => En::F64Load(mem(memarg)),
|
||||||
De::I32Load8S { memarg } => En::I32Load8_S(mem(memarg)),
|
De::I32Load8S { memarg } => En::I32Load8S(mem(memarg)),
|
||||||
De::I32Load8U { memarg } => En::I32Load8_U(mem(memarg)),
|
De::I32Load8U { memarg } => En::I32Load8U(mem(memarg)),
|
||||||
De::I32Load16S { memarg } => En::I32Load16_S(mem(memarg)),
|
De::I32Load16S { memarg } => En::I32Load16S(mem(memarg)),
|
||||||
De::I32Load16U { memarg } => En::I32Load16_U(mem(memarg)),
|
De::I32Load16U { memarg } => En::I32Load16U(mem(memarg)),
|
||||||
De::I64Load8S { memarg } => En::I64Load8_S(mem(memarg)),
|
De::I64Load8S { memarg } => En::I64Load8S(mem(memarg)),
|
||||||
De::I64Load8U { memarg } => En::I64Load8_U(mem(memarg)),
|
De::I64Load8U { memarg } => En::I64Load8U(mem(memarg)),
|
||||||
De::I64Load16S { memarg } => En::I64Load16_S(mem(memarg)),
|
De::I64Load16S { memarg } => En::I64Load16S(mem(memarg)),
|
||||||
De::I64Load16U { memarg } => En::I64Load16_U(mem(memarg)),
|
De::I64Load16U { memarg } => En::I64Load16U(mem(memarg)),
|
||||||
De::I64Load32S { memarg } => En::I64Load32_S(mem(memarg)),
|
De::I64Load32S { memarg } => En::I64Load32S(mem(memarg)),
|
||||||
De::I64Load32U { memarg } => En::I64Load32_U(mem(memarg)),
|
De::I64Load32U { memarg } => En::I64Load32U(mem(memarg)),
|
||||||
De::I32Store { memarg } => En::I32Store(mem(memarg)),
|
De::I32Store { memarg } => En::I32Store(mem(memarg)),
|
||||||
De::I64Store { memarg } => En::I64Store(mem(memarg)),
|
De::I64Store { memarg } => En::I64Store(mem(memarg)),
|
||||||
De::F32Store { memarg } => En::F32Store(mem(memarg)),
|
De::F32Store { memarg } => En::F32Store(mem(memarg)),
|
||||||
@@ -722,7 +828,7 @@ fn remap_function(
|
|||||||
De::RefNull { .. } | De::RefIsNull { .. } | De::RefFunc { .. } => todo!(),
|
De::RefNull { .. } | De::RefIsNull { .. } | De::RefFunc { .. } => todo!(),
|
||||||
De::I32Eqz => En::I32Eqz,
|
De::I32Eqz => En::I32Eqz,
|
||||||
De::I32Eq => En::I32Eq,
|
De::I32Eq => En::I32Eq,
|
||||||
De::I32Ne => En::I32Neq,
|
De::I32Ne => En::I32Ne,
|
||||||
De::I32LtS => En::I32LtS,
|
De::I32LtS => En::I32LtS,
|
||||||
De::I32LtU => En::I32LtU,
|
De::I32LtU => En::I32LtU,
|
||||||
De::I32GtS => En::I32GtS,
|
De::I32GtS => En::I32GtS,
|
||||||
@@ -733,7 +839,7 @@ fn remap_function(
|
|||||||
De::I32GeU => En::I32GeU,
|
De::I32GeU => En::I32GeU,
|
||||||
De::I64Eqz => En::I64Eqz,
|
De::I64Eqz => En::I64Eqz,
|
||||||
De::I64Eq => En::I64Eq,
|
De::I64Eq => En::I64Eq,
|
||||||
De::I64Ne => En::I64Neq,
|
De::I64Ne => En::I64Ne,
|
||||||
De::I64LtS => En::I64LtS,
|
De::I64LtS => En::I64LtS,
|
||||||
De::I64LtU => En::I64LtU,
|
De::I64LtU => En::I64LtU,
|
||||||
De::I64GtS => En::I64GtS,
|
De::I64GtS => En::I64GtS,
|
||||||
@@ -743,13 +849,13 @@ fn remap_function(
|
|||||||
De::I64GeS => En::I64GeS,
|
De::I64GeS => En::I64GeS,
|
||||||
De::I64GeU => En::I64GeU,
|
De::I64GeU => En::I64GeU,
|
||||||
De::F32Eq => En::F32Eq,
|
De::F32Eq => En::F32Eq,
|
||||||
De::F32Ne => En::F32Neq,
|
De::F32Ne => En::F32Ne,
|
||||||
De::F32Lt => En::F32Lt,
|
De::F32Lt => En::F32Lt,
|
||||||
De::F32Gt => En::F32Gt,
|
De::F32Gt => En::F32Gt,
|
||||||
De::F32Le => En::F32Le,
|
De::F32Le => En::F32Le,
|
||||||
De::F32Ge => En::F32Ge,
|
De::F32Ge => En::F32Ge,
|
||||||
De::F64Eq => En::F64Eq,
|
De::F64Eq => En::F64Eq,
|
||||||
De::F64Ne => En::F64Neq,
|
De::F64Ne => En::F64Ne,
|
||||||
De::F64Lt => En::F64Lt,
|
De::F64Lt => En::F64Lt,
|
||||||
De::F64Gt => En::F64Gt,
|
De::F64Gt => En::F64Gt,
|
||||||
De::F64Le => En::F64Le,
|
De::F64Le => En::F64Le,
|
||||||
@@ -856,6 +962,8 @@ fn remap_function(
|
|||||||
De::I64TruncSatF32U => En::I64TruncSatF32U,
|
De::I64TruncSatF32U => En::I64TruncSatF32U,
|
||||||
De::I64TruncSatF64S => En::I64TruncSatF64S,
|
De::I64TruncSatF64S => En::I64TruncSatF64S,
|
||||||
De::I64TruncSatF64U => En::I64TruncSatF64U,
|
De::I64TruncSatF64U => En::I64TruncSatF64U,
|
||||||
|
De::MemoryCopy { src_mem, dst_mem } => En::MemoryCopy { src_mem, dst_mem },
|
||||||
|
De::MemoryFill { mem } => En::MemoryFill(mem),
|
||||||
other => bail!("Unsupported instruction {:?}", other),
|
other => bail!("Unsupported instruction {:?}", other),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
1
uw8-window/.gitignore
vendored
Normal file
1
uw8-window/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
2172
uw8-window/Cargo.lock
generated
Normal file
2172
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]
|
||||||
|
winit = "0.27.5"
|
||||||
|
env_logger = "0.10"
|
||||||
|
log = "0.4"
|
||||||
|
pico-args = "0.5"
|
||||||
|
wgpu = "0.15"
|
||||||
|
pollster = "0.2.5"
|
||||||
|
bytemuck = { version = "1.13", features = [ "derive" ] }
|
||||||
|
anyhow = "1"
|
||||||
|
minifb = { version = "0.23.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 offset = max(0.0, abs(offset) - 0.4);
|
||||||
|
return 1.0 / (1.0 + offset * offset * 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);
|
||||||
|
}
|
||||||
|
|
||||||
545
uw8-window/src/gpu/mod.rs
Normal file
545
uw8-window/src/gpu/mod.rs
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
use crate::{Input, WindowConfig, WindowImpl};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::{num::NonZeroU32, 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(640u32, 480))
|
||||||
|
.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: NonZeroU32::new(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: NonZeroU32::new(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));
|
||||||
|
}
|
||||||
117
uw8-window/src/lib.rs
Normal file
117
uw8-window/src/lib.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
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(config: WindowConfig) -> Result<Window> {
|
||||||
|
let fps_counter = if config.fps_counter {
|
||||||
|
Some(FpsCounter {
|
||||||
|
start: Instant::now(),
|
||||||
|
num_frames: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if config.enable_gpu {
|
||||||
|
match gpu::Window::new(config) {
|
||||||
|
Ok(window) => {
|
||||||
|
return Ok(Window {
|
||||||
|
inner: Box::new(window),
|
||||||
|
fps_counter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!(
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowConfig {
|
||||||
|
fn default() -> WindowConfig {
|
||||||
|
WindowConfig {
|
||||||
|
enable_gpu: true,
|
||||||
|
filter: 5,
|
||||||
|
fullscreen: false,
|
||||||
|
fps_counter: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowConfig {
|
||||||
|
pub fn parse_arguments(&mut self, args: &mut pico_args::Arguments) {
|
||||||
|
self.enable_gpu = !args.contains("--no-gpu");
|
||||||
|
if let Some(filter) = args.opt_value_from_str::<_, String>("--filter").unwrap() {
|
||||||
|
self.filter = match filter.as_str() {
|
||||||
|
"1" | "nearest" => 1,
|
||||||
|
"2" | "fast_crt" => 2,
|
||||||
|
"3" | "ss_crt" => 3,
|
||||||
|
"4" | "chromatic" => 4,
|
||||||
|
"5" | "auto_crt" => 5,
|
||||||
|
o => {
|
||||||
|
println!("Unknown --filter '{}'", o);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.fullscreen = args.contains("--fullscreen");
|
||||||
|
self.fps_counter = args.contains("--fps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Input {
|
||||||
|
pub gamepads: [u8; 4],
|
||||||
|
pub reset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WindowImpl {
|
||||||
|
fn begin_frame(&mut self) -> Input;
|
||||||
|
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant);
|
||||||
|
fn is_open(&self) -> bool;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
web/build-run-web
Executable file
2
web/build-run-web
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm -rf .parcel-cache && yarn parcel build src/run-web.html && cp dist/run-web.html ../src/
|
||||||
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>
|
||||||
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);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user