mirror of
https://github.com/exoticorn/microw8.git
synced 2026-06-28 05:49:37 +02:00
Compare commits
65 Commits
v0.2.0-rc3
...
b23248f359
| Author | SHA1 | Date | |
|---|---|---|---|
| b23248f359 | |||
| a651107104 | |||
| 22c35e37f4 | |||
| 805c939097 | |||
| 440e150896 | |||
| 77b2e27346 | |||
| 09e4fcbf14 | |||
| dbeb242fb2 | |||
| 1d89ef8613 | |||
| c7be8be2c4 | |||
| d9f9500b76 | |||
| 4fe4bce0ad | |||
| fe86153562 | |||
| c9dadaca2e | |||
| 5dc3e281ce | |||
| ce3afb821f | |||
| 2652a351ad | |||
| 9109722409 | |||
| f861c262a1 | |||
| bbfb5eba49 | |||
| 9e19ff1761 | |||
| 5d85aeac09 | |||
| 30eb953d5d | |||
| abdf780533 | |||
| fc05a54e2c | |||
| b33099c828 | |||
| d478d3ad49 | |||
| 502852e59a | |||
| 5efa8b3465 | |||
| daf2a02cd8 | |||
| 8d5374a867 | |||
| 142b6a4c15 | |||
| 877fceb089 | |||
| f0ba0f2b99 | |||
| 760664eb77 | |||
| 465e66ff4b | |||
| e4579d81bc | |||
| 1f5042059c | |||
| 499bb02f2c | |||
| 57a92ba79a | |||
| 7ec1e68a00 | |||
| 539d19e0d7 | |||
| e9a5f702b4 | |||
| ba0b037ec2 | |||
| 0130d1c906 | |||
| 379ece5cbf | |||
| c9c5cb76bd | |||
| a6d6615231 | |||
| fbc86fa78d | |||
| eb724e8785 | |||
| f559c5b7d4 | |||
| 9dabf75732 | |||
| b0adf7748d | |||
| 7aa70ef39d | |||
| 2ce91ef49c | |||
| 7caad08b7c | |||
| 1f6de62e5d | |||
| caeaa82787 | |||
| e0450c9039 | |||
| 95d0d92a6f | |||
| 7a6dd0ab6d | |||
| e7a00dd9c6 | |||
| a02243d98c | |||
| 599873890a | |||
| 8e9bb002bc |
@@ -30,9 +30,9 @@ jobs:
|
|||||||
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Cache build dirs
|
- name: Cache build dirs
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: uw8-${{ matrix.build }}
|
name: uw8-${{ matrix.build }}
|
||||||
path: target/release/${{ matrix.exe }}
|
path: target/release/${{ matrix.exe }}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: build_and_deploy
|
- name: build_and_deploy
|
||||||
uses: shalzz/zola-deploy-action@v0.14.1
|
uses: shalzz/zola-deploy-action@70a101a14bbdeed13e7a42a9ed06b35c9e9e826e
|
||||||
env:
|
env:
|
||||||
# Target branch
|
# Target branch
|
||||||
PAGES_BRANCH: gh-pages
|
PAGES_BRANCH: gh-pages
|
||||||
BUILD_DIR: site
|
BUILD_DIR: site
|
||||||
# Provide personal access token
|
# Provide personal access token
|
||||||
TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
Generated
+2228
-1113
File diff suppressed because it is too large
Load Diff
+15
-13
@@ -1,29 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.2.0-rc3"
|
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]
|
[features]
|
||||||
default = ["native", "browser"]
|
default = ["native", "browser"]
|
||||||
native = ["wasmtime", "minifb", "cpal", "rubato"]
|
native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
|
||||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = { version = "0.35.3", optional = true }
|
wasmtime = { version = "12.0.0", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
|
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 = "c22297e" }
|
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||||
|
pico-args = "0.5"
|
||||||
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
|
||||||
wat = "1"
|
wat = "1"
|
||||||
uw8-tool = { path = "uw8-tool" }
|
uw8-tool = { path = "uw8-tool" }
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
warp = { version = "0.3.2", optional = true }
|
warp = { version = "0.3.5", optional = true }
|
||||||
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
tokio = { version = "1.32.0", features = ["sync", "rt"], optional = true }
|
||||||
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
tokio-stream = { version = "0.1.14", features = ["sync"], optional = true }
|
||||||
webbrowser = { version = "0.6.0", optional = true }
|
webbrowser = { version = "0.8.11", optional = true }
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
cpal = { version = "0.13.5", optional = true }
|
cpal = { version = "0.15.2", optional = true }
|
||||||
rubato = { version = "0.11.0", optional = true }
|
rubato = { version = "0.14.0", optional = true }
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
|
|||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
|
||||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
|
||||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
|
||||||
|
|
||||||
The download includes
|
The download includes
|
||||||
|
|
||||||
@@ -43,6 +43,27 @@ Options:
|
|||||||
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
|
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
|
||||||
-o FILE, --output FILE : Write the loaded and optionally packed cart back to disk.
|
-o FILE, --output FILE : Write the loaded and optionally packed cart back to disk.
|
||||||
|
|
||||||
|
when using the native runtime:
|
||||||
|
|
||||||
|
-m, --no-audio : Disable audio, also reduces cpu load a bit
|
||||||
|
--no-gpu : Force old cpu-only window code
|
||||||
|
--filter FILTER : Select an upscale filter at startup
|
||||||
|
--fullscreen : Start in fullscreen mode
|
||||||
|
|
||||||
|
Note that the cpu-only window does not support fullscreen nor upscale filters.
|
||||||
|
|
||||||
|
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
|
||||||
|
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
|
||||||
|
you can just pass "--filter nearest" or "--filter 1".
|
||||||
|
|
||||||
|
The upscale filter options are:
|
||||||
|
1, nearest : Anti-aliased nearest filter
|
||||||
|
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
|
||||||
|
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
|
||||||
|
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
|
||||||
|
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
|
||||||
|
|
||||||
|
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
|
||||||
|
|
||||||
uw8 pack [<options>] <infile> <outfile>
|
uw8 pack [<options>] <infile> <outfile>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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) * isButtonPressed(4 <| cls(4)) as f32;
|
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
|
||||||
@@ -30,6 +30,8 @@ export fn upd() {
|
|||||||
|
|
||||||
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;
|
||||||
@@ -43,6 +45,9 @@ 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);
|
||||||
|
|
||||||
|
0?86 = py < zero;
|
||||||
|
3?86 = 32 - py as i32;
|
||||||
|
|
||||||
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
|
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
|
||||||
py = py + s;
|
py = py + s;
|
||||||
pz = pz + 1;
|
pz = pz + 1;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ 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.circle_outline" fn circle_outline(f32, f32, f32, i32);
|
||||||
import "env.exp" fn exp(f32) -> f32;
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
import "env.playNote" fn playNote(i32, i32);
|
import "env.playNote" fn playNote(i32, i32);
|
||||||
|
import "env.sndGes" fn sndGes(i32) -> f32;
|
||||||
|
|
||||||
const TIME_MS = 0x40;
|
const TIME_MS = 0x40;
|
||||||
const GAMEPAD = 0x44;
|
const GAMEPAD = 0x44;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
(import "env" "circle_outline" (func $circle_outline (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" "exp" (func $exp (param f32) (result f32)))
|
||||||
(import "env" "playNote" (func $playNote (param i32) (param i32)))
|
(import "env" "playNote" (func $playNote (param i32) (param i32)))
|
||||||
|
(import "env" "sndGes" (func $sndGes (param i32) (result f32)))
|
||||||
|
|
||||||
;; to use defines, include this file with a preprocessor
|
;; to use defines, include this file with a preprocessor
|
||||||
;; like gpp (https://logological.org/gpp).
|
;; like gpp (https://logological.org/gpp).
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
Generated
+149
-39
@@ -110,9 +110,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.3.0"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
@@ -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=c22297e#c22297ea82977ac06373d629fb273a795322d788"
|
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 0.10.0",
|
"wasm-encoder 0.10.0",
|
||||||
"wasmparser 0.83.0",
|
"wasmparser 0.83.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible_collections"
|
name = "fallible_collections"
|
||||||
version = "0.4.4"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52db5973b6a19247baf19b30f41c23a1bfffc2e9ce0a5db2f60e3cd5dc8895f7"
|
checksum = "3f57ccc32870366ae684be48b32a1a2e196f98a42a9b4361fe77e13fd4a34755"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -190,9 +197,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
]
|
]
|
||||||
@@ -212,6 +219,26 @@ version = "2.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -225,17 +252,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "lexopt"
|
||||||
version = "0.2.112"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.139"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lodepng"
|
name = "lodepng"
|
||||||
version = "3.4.7"
|
version = "3.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24844d5c0b922ddd52fb5bf0964a4c7f8e799a946ec01bb463771eb04fc1a323"
|
checksum = "f0ad39f75bbaa4b10bb6f2316543632a8046a5bcf9c785488d79720b21f044f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
"fallible_collections",
|
"fallible_collections",
|
||||||
"flate2",
|
"flate2",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -253,12 +287,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.4"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
"autocfg",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -288,12 +321,24 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "pico-args"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platform"
|
name = "platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -330,9 +375,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.31"
|
version = "0.8.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a374af9a0e5fdcdd98c1c7b64f05004f9ea2555b6c75f211daa81268a3c50f1"
|
checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
@@ -357,6 +402,26 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
@@ -376,6 +441,36 @@ dependencies = [
|
|||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@@ -390,13 +485,24 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upkr"
|
name = "upkr"
|
||||||
version = "0.1.0"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0"
|
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cdivsufsort",
|
"cdivsufsort",
|
||||||
"pbr",
|
"lexopt",
|
||||||
"pico-args",
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -405,11 +511,11 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"pbr",
|
"pbr",
|
||||||
"pico-args",
|
"pico-args 0.5.0",
|
||||||
"upkr",
|
"upkr",
|
||||||
"walrus",
|
"walrus",
|
||||||
"wasm-encoder 0.8.0",
|
"wasm-encoder 0.22.0",
|
||||||
"wasmparser 0.81.0",
|
"wasmparser 0.99.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -452,18 +558,18 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.8.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.10.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
@@ -474,18 +580,22 @@ version = "0.77.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasmparser"
|
|
||||||
version = "0.81.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.83.0"
|
version = "0.83.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.99.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|||||||
+2
-2
@@ -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="c22297e" }
|
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.
@@ -10,7 +10,7 @@ const GesState.Size = GesState.Filter + 8*4;
|
|||||||
const GesStateOffset = 32;
|
const GesStateOffset = 32;
|
||||||
const GesBufferOffset = 32 + GesState.Size;
|
const GesBufferOffset = 32 + GesState.Size;
|
||||||
|
|
||||||
export fn gesSnd(t: i32) -> f32 {
|
export fn sndGes(t: i32) -> f32 {
|
||||||
let baseAddr = 0!0x12c78;
|
let baseAddr = 0!0x12c78;
|
||||||
if !(t & 127) {
|
if !(t & 127) {
|
||||||
let i: i32;
|
let i: i32;
|
||||||
@@ -62,7 +62,6 @@ export fn gesSnd(t: i32) -> f32 {
|
|||||||
let phase = channelState!GesChannelState.Phase;
|
let phase = channelState!GesChannelState.Phase;
|
||||||
|
|
||||||
let inline pulseWidth = channelReg?1;
|
let inline pulseWidth = channelReg?1;
|
||||||
let phaseShift = (pulseWidth - 128) * 255;
|
|
||||||
let invPhaseInc = 1 as f32 / phaseInc as f32;
|
let invPhaseInc = 1 as f32 / phaseInc as f32;
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
@@ -131,7 +130,7 @@ export fn gesSnd(t: i32) -> f32 {
|
|||||||
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||||
|
|
||||||
let phase = channelState!GesChannelState.Phase;
|
let phase = channelState!GesChannelState.Phase;
|
||||||
if modSrc > ch {
|
if modSrc < ch {
|
||||||
phase = phase - (phaseInc << 6);
|
phase = phase - (phaseInc << 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+75
-26
@@ -62,11 +62,7 @@ export fn cls(col: i32) {
|
|||||||
textCursorX = 0;
|
textCursorX = 0;
|
||||||
textCursorY = 0;
|
textCursorY = 0;
|
||||||
outputChannel = 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) {
|
||||||
@@ -90,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,14 +372,7 @@ export fn printChar(char: i32) {
|
|||||||
global mut controlCodeLength = 0;
|
global mut controlCodeLength = 0;
|
||||||
|
|
||||||
fn printSingleChar(char: i32) {
|
fn printSingleChar(char: i32) {
|
||||||
if char >= 4 & char <= 6 {
|
if outputChannel >= 2 & (char < 4 | char > 6) {
|
||||||
outputChannel = char - 4;
|
|
||||||
textCursorX = 0;
|
|
||||||
textCursorY = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if outputChannel >= 2 {
|
|
||||||
logChar(char);
|
logChar(char);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -343,6 +390,15 @@ fn printSingleChar(char: i32) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if char >= 4 & char <= 6 {
|
||||||
|
outputChannel = char - 4;
|
||||||
|
if !outputChannel {
|
||||||
|
textCursorX = 0;
|
||||||
|
textCursorY = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if char == 7 {
|
if char == 7 {
|
||||||
80?0 = 80?0 ^ 2;
|
80?0 = 80?0 ^ 2;
|
||||||
return;
|
return;
|
||||||
@@ -546,13 +602,6 @@ export fn endFrame() {
|
|||||||
68!4 = 68!0;
|
68!4 = 68!0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memclr(base: i32, size: i32) {
|
|
||||||
loop bytes {
|
|
||||||
(base + (size := size - 1))?0 = 0;
|
|
||||||
branch_if size: bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start fn setup() {
|
start fn setup() {
|
||||||
let i: i32 = 12*16*3-1;
|
let i: i32 = 12*16*3-1;
|
||||||
let avg: f32;
|
let avg: f32;
|
||||||
@@ -585,9 +634,9 @@ start fn setup() {
|
|||||||
branch_if (i := i - 1) >= 0: expand_sweetie;
|
branch_if (i := i - 1) >= 0: expand_sweetie;
|
||||||
}
|
}
|
||||||
|
|
||||||
memclr(0, 64);
|
memory.fill(0, 0, 64);
|
||||||
memclr(112, 8);
|
memory.fill(112, 0, 8);
|
||||||
memclr(0x14000, 0x2c000);
|
memory.fill(0x14000, 0, 0x2c000);
|
||||||
|
|
||||||
|
|
||||||
cls(0);
|
cls(0);
|
||||||
|
|||||||
+45
-57
@@ -14,12 +14,14 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
|
|||||||
* Memory: 256KB
|
* Memory: 256KB
|
||||||
* Gamepad input (D-Pad + 4 Buttons)
|
* Gamepad input (D-Pad + 4 Buttons)
|
||||||
|
|
||||||
|
For detailed [documentation see here](docs).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
* [Fireworks](v0.1.2#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
* [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound
|
||||||
* [Skip Ahead](v0.1.2#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
|
* [Fireworks](v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
||||||
* [OhNoAnotherTunnel](v0.1.2#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
* [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.1.2#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
|
* [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.1.2#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
* [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:
|
Examplers for older versions:
|
||||||
|
|
||||||
@@ -29,64 +31,50 @@ Examplers for older versions:
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
### v0.2.0-rc3
|
### v0.2.2
|
||||||
|
|
||||||
* [Web runtime](v0.2.0-rc3)
|
* [Web runtime](v0.2.2)
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-linux.tgz)
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-linux.tgz)
|
||||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-macos.tgz)
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-macos.tgz)
|
||||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.2/microw8-0.2.2-windows.zip)
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|
||||||
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
|
* call `start` function after loading cart if the cart exports one
|
||||||
consecutive frame numbers, provided the module can be run at 60 fps
|
* fix `sndGes` having the wrong name and not being included in the auto imports
|
||||||
|
* fix control codes 4-6 (change text output mode) being invoked when used as parameters in other control sequences
|
||||||
|
* only open browser window once a cart was compiled sucessfully when running with `-b`
|
||||||
|
|
||||||
|
### v0.2.1
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.1)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.1/microw8-0.2.1-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* new gpu accelerated renderer with (optional) crt filter
|
||||||
|
* optimized `hline` function, a big speed-up when drawing large filled circles or rectangles
|
||||||
|
* print fractional size of packed `uw8` cart
|
||||||
|
|
||||||
|
### v0.2.0
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* [add sound support!](docs#sound)
|
||||||
* add support to redirect text output to the console for debugging using control code 6
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
* update curlywas:
|
* update curlywas:
|
||||||
* * add support for `else if`
|
* add support for `else if`
|
||||||
* * add support for escape sequences in strings
|
* add support for escape sequences in strings
|
||||||
* * add support for char literals
|
* add support for char literals
|
||||||
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
* 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-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
|
|
||||||
|
|
||||||
### Older versions
|
### Older versions
|
||||||
|
|
||||||
|
|||||||
+63
-21
@@ -12,6 +12,9 @@ at offset 120 in memory with the 32bpp palette located at 0x13000.
|
|||||||
|
|
||||||
The memory has to be imported as `env` `memory` and has a maximum size of 256kb (4 pages).
|
The memory has to be imported as `env` `memory` and has a maximum size of 256kb (4 pages).
|
||||||
|
|
||||||
|
If the module exports a function called `start`, it will be called once after the module is
|
||||||
|
loaded.
|
||||||
|
|
||||||
# Memory map
|
# Memory map
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -225,30 +228,45 @@ Moving/printing past any border does not cause any special operation, the cursor
|
|||||||
Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
|
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.
|
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.
|
||||||
|
|
||||||
| Code | Parameters | Operation |
|
| Code | Parameters | Operation |
|
||||||
| ----- | ---------- | ------------------------------------ |
|
| ----- | ---------- | ------------------------------------------ |
|
||||||
| 0 | - | Nop |
|
| 0 | - | Nop |
|
||||||
| 1 | char | Print char (including control chars) |
|
| 1 | char | Print char (including control chars) |
|
||||||
| 2-3 | - | Reserved |
|
| 2-3 | - | Reserved |
|
||||||
| 4 | - | Switch to normal mode |
|
| 4 | - | Switch to normal mode, reset cursor to 0,0 |
|
||||||
| 5 | - | Switch to graphics mode |
|
| 5 | - | Switch to graphics mode |
|
||||||
| 6 | - | Reserved |
|
| 6 | - | Switch output to (debug) console |
|
||||||
| 7 | - | Bell / trigger sound channel 0 |
|
| 7 | - | Bell / trigger sound channel 0 |
|
||||||
| 8 | - | Move cursor left |
|
| 8 | - | Move cursor left |
|
||||||
| 9 | - | Move cursor right |
|
| 9 | - | Move cursor right |
|
||||||
| 10 | - | Move cursor down |
|
| 10 | - | Move cursor down |
|
||||||
| 11 | - | Move cursor up |
|
| 11 | - | Move cursor up |
|
||||||
| 12 | - | do `cls(background_color)` |
|
| 12 | - | do `cls(background_color)` |
|
||||||
| 13 | - | Move cursor to the left border |
|
| 13 | - | Move cursor to the left border |
|
||||||
| 14 | color | Set the background color |
|
| 14 | color | Set the background color |
|
||||||
| 15 | color | Set the text color |
|
| 15 | color | Set the text color |
|
||||||
| 16-23 | - | Reserved |
|
| 16-23 | - | Reserved |
|
||||||
| 24 | - | Swap text/background colors |
|
| 24 | - | Swap text/background colors |
|
||||||
| 25-30 | - | Reserved |
|
| 25-30 | - | Reserved |
|
||||||
| 31 | x, y | Set cursor position (*) |
|
| 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.
|
(*) 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)
|
### 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.
|
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.
|
||||||
@@ -421,6 +439,30 @@ and execution of the cart is stopped. Defaults to 30 (0.5s)
|
|||||||
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
|
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
|
||||||
* `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk.
|
* `-o FILE`, `--output FILE`: Write the loaded and optionally packed cart back to disk.
|
||||||
|
|
||||||
|
when using the native runtime:
|
||||||
|
|
||||||
|
* `-m`, `--no-audio`: Disable audio, also reduces cpu load a bit
|
||||||
|
* `--no-gpu`: Force old cpu-only window code
|
||||||
|
* `--filter FILTER`: Select an upscale filter at startup
|
||||||
|
* `--fullscreen`: Start in fullscreen mode
|
||||||
|
|
||||||
|
Note that the cpu-only window does not support fullscreen nor upscale filters.
|
||||||
|
|
||||||
|
Unless --no-gpu is given, uw8 will first try to open a gpu accelerated window, falling back to the old cpu-only window if that fails.
|
||||||
|
Therefore you should rarely need to manually pass --no-gpu. If you prefer the old pixel doubling look to the now default crt filter,
|
||||||
|
you can just pass `--filter nearest` or `--filter 1`.
|
||||||
|
|
||||||
|
The upscale filter options are:
|
||||||
|
```
|
||||||
|
1, nearest : Anti-aliased nearest filter
|
||||||
|
2, fast_crt : Very simple, cheap crt filter, not very good below a window size of 960x720
|
||||||
|
3, ss_crt : Super sampled crt filter, a little more demanding on the GPU but scales well to smaller window sizes
|
||||||
|
4, chromatic_crt : Variant of fast_crt with a slight offset of the three color dots of a pixel, still pretty cheap
|
||||||
|
5, auto_crt (default) : ss_crt below 960x720, chromatic_crt otherwise
|
||||||
|
```
|
||||||
|
|
||||||
|
You can switch the upscale filter at any time using the keys 1-5. You can toggle fullscreen with F.
|
||||||
|
|
||||||
## `uw8 pack`
|
## `uw8 pack`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|||||||
@@ -2,6 +2,69 @@
|
|||||||
description = "Versions"
|
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
|
### v0.1.2
|
||||||
|
|
||||||
* [Web runtime](v0.1.2)
|
* [Web runtime](v0.1.2)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
||||||
</section>
|
</section>
|
||||||
<a href="v0.1.2">
|
<a href="v0.2.2">
|
||||||
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+29
-13
@@ -1,9 +1,13 @@
|
|||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use notify::{DebouncedEvent, RecommendedWatcher, Watcher};
|
use notify_debouncer_mini::{
|
||||||
|
new_debouncer,
|
||||||
|
notify::{self, RecommendedWatcher},
|
||||||
|
DebouncedEvent, DebouncedEventKind, Debouncer,
|
||||||
|
};
|
||||||
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
pub struct FileWatcher {
|
pub struct FileWatcher {
|
||||||
watcher: RecommendedWatcher,
|
debouncer: Debouncer<RecommendedWatcher>,
|
||||||
watched_files: BTreeSet<PathBuf>,
|
watched_files: BTreeSet<PathBuf>,
|
||||||
directories: BTreeSet<PathBuf>,
|
directories: BTreeSet<PathBuf>,
|
||||||
rx: mpsc::Receiver<DebouncedEvent>,
|
rx: mpsc::Receiver<DebouncedEvent>,
|
||||||
@@ -12,9 +16,18 @@ pub struct FileWatcher {
|
|||||||
impl FileWatcher {
|
impl FileWatcher {
|
||||||
pub fn new() -> Result<FileWatcher> {
|
pub fn new() -> Result<FileWatcher> {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
let debouncer = new_debouncer(Duration::from_millis(100), move |res| match res {
|
||||||
|
Ok(events) => {
|
||||||
|
for event in events {
|
||||||
|
let _ = tx.send(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error watching for file changes: {err}");
|
||||||
|
}
|
||||||
|
})?;
|
||||||
Ok(FileWatcher {
|
Ok(FileWatcher {
|
||||||
watcher,
|
debouncer,
|
||||||
watched_files: BTreeSet::new(),
|
watched_files: BTreeSet::new(),
|
||||||
directories: BTreeSet::new(),
|
directories: BTreeSet::new(),
|
||||||
rx,
|
rx,
|
||||||
@@ -26,7 +39,8 @@ impl FileWatcher {
|
|||||||
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
|
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
|
||||||
|
|
||||||
if !self.directories.contains(parent) {
|
if !self.directories.contains(parent) {
|
||||||
self.watcher
|
self.debouncer
|
||||||
|
.watcher()
|
||||||
.watch(parent, notify::RecursiveMode::NonRecursive)?;
|
.watch(parent, notify::RecursiveMode::NonRecursive)?;
|
||||||
self.directories.insert(parent.to_path_buf());
|
self.directories.insert(parent.to_path_buf());
|
||||||
}
|
}
|
||||||
@@ -36,16 +50,18 @@ impl FileWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
||||||
let event = self.rx.try_recv();
|
match self.rx.try_recv() {
|
||||||
match event {
|
Ok(event) => match event.kind {
|
||||||
Ok(DebouncedEvent::Create(path) | DebouncedEvent::Write(path)) => {
|
DebouncedEventKind::Any => {
|
||||||
let handle = same_file::Handle::from_path(&path)?;
|
let handle = same_file::Handle::from_path(&event.path)?;
|
||||||
for file in &self.watched_files {
|
for file in &self.watched_files {
|
||||||
if handle == same_file::Handle::from_path(file)? {
|
if handle == same_file::Handle::from_path(file)? {
|
||||||
return Ok(Some(path));
|
return Ok(Some(event.path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => (),
|
||||||
|
},
|
||||||
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
Err(mpsc::TryRecvError::Disconnected) => bail!("File watcher disconnected"),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -14,9 +14,6 @@ use anyhow::Result;
|
|||||||
|
|
||||||
pub trait Runtime {
|
pub trait Runtime {
|
||||||
fn is_open(&self) -> bool;
|
fn is_open(&self) -> bool;
|
||||||
fn set_timeout(&mut self, _timeout: u32) {
|
|
||||||
eprintln!("Warning: runtime doesn't support timeout");
|
|
||||||
}
|
|
||||||
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
fn load(&mut self, module_data: &[u8]) -> Result<()>;
|
||||||
fn run_frame(&mut self) -> Result<()>;
|
fn run_frame(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-7
@@ -13,6 +13,7 @@ use uw8::RunWebServer;
|
|||||||
use uw8::Runtime;
|
use uw8::Runtime;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default()).init();
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
// try to enable ansi support in win10 cmd shell
|
// try to enable ansi support in win10 cmd shell
|
||||||
@@ -52,6 +53,7 @@ fn main() -> Result<()> {
|
|||||||
#[cfg(any(feature = "native", feature = "browser"))]
|
#[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 timeout: Option<u32> = args.opt_value_from_str(["-t", "--timeout"])?;
|
||||||
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
@@ -79,7 +81,17 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
let run_browser = args.contains(["-b", "--browser"]) || true;
|
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||||
|
|
||||||
let disable_audio = args.contains(["-m", "--disable-audio"]);
|
#[allow(unused)]
|
||||||
|
let disable_audio = args.contains(["-m", "--no-audio"]);
|
||||||
|
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
let window_config = {
|
||||||
|
let mut config = uw8_window::WindowConfig::default();
|
||||||
|
if !run_browser {
|
||||||
|
config.parse_arguments(&mut args);
|
||||||
|
}
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
@@ -92,7 +104,7 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
unimplemented!();
|
unimplemented!();
|
||||||
#[cfg(feature = "native")]
|
#[cfg(feature = "native")]
|
||||||
{
|
{
|
||||||
let mut microw8 = MicroW8::new()?;
|
let mut microw8 = MicroW8::new(timeout, window_config)?;
|
||||||
if disable_audio {
|
if disable_audio {
|
||||||
microw8.disable_audio();
|
microw8.disable_audio();
|
||||||
}
|
}
|
||||||
@@ -105,10 +117,6 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
Box::new(RunWebServer::new())
|
Box::new(RunWebServer::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(timeout) = timeout {
|
|
||||||
runtime.set_timeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut first_run = true;
|
let mut first_run = true;
|
||||||
|
|
||||||
while runtime.is_open() {
|
while runtime.is_open() {
|
||||||
@@ -167,7 +175,11 @@ fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>
|
|||||||
|
|
||||||
if let Some(ref pack_config) = config.pack {
|
if let Some(ref pack_config) = config.pack {
|
||||||
cart = uw8_tool::pack(&cart, pack_config)?;
|
cart = uw8_tool::pack(&cart, pack_config)?;
|
||||||
println!("packed size: {} bytes", cart.len());
|
println!(
|
||||||
|
"\npacked size: {} bytes ({:.2})",
|
||||||
|
cart.len(),
|
||||||
|
uw8_tool::compressed_size(&cart)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref path) = config.output_path {
|
if let Some(ref path) = config.output_path {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+218
-226
@@ -4,31 +4,21 @@ use std::{thread, time::Instant};
|
|||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cpal::traits::*;
|
use cpal::traits::*;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
|
||||||
use rubato::Resampler;
|
use rubato::Resampler;
|
||||||
|
use uw8_window::{Window, WindowConfig};
|
||||||
use wasmtime::{
|
use wasmtime::{
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
Engine, Func, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||||
};
|
};
|
||||||
|
|
||||||
static GAMEPAD_KEYS: &[Key] = &[
|
|
||||||
Key::Up,
|
|
||||||
Key::Down,
|
|
||||||
Key::Left,
|
|
||||||
Key::Right,
|
|
||||||
Key::Z,
|
|
||||||
Key::X,
|
|
||||||
Key::A,
|
|
||||||
Key::S,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub struct MicroW8 {
|
pub struct MicroW8 {
|
||||||
|
window: Window,
|
||||||
|
stream: Option<cpal::Stream>,
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
loader_module: Module,
|
loader_module: Module,
|
||||||
window: Window,
|
|
||||||
window_buffer: Vec<u32>,
|
|
||||||
instance: Option<UW8Instance>,
|
|
||||||
timeout: u32,
|
|
||||||
disable_audio: bool,
|
disable_audio: bool,
|
||||||
|
module_data: Option<Vec<u8>>,
|
||||||
|
timeout: u32,
|
||||||
|
instance: Option<UW8Instance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UW8Instance {
|
struct UW8Instance {
|
||||||
@@ -37,10 +27,8 @@ struct UW8Instance {
|
|||||||
end_frame: TypedFunc<(), ()>,
|
end_frame: TypedFunc<(), ()>,
|
||||||
update: Option<TypedFunc<(), ()>>,
|
update: Option<TypedFunc<(), ()>>,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
next_frame: Instant,
|
|
||||||
module: Vec<u8>,
|
|
||||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
sound: Option<Uw8Sound>,
|
sound_tx: Option<mpsc::SyncSender<RegisterUpdate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
impl Drop for UW8Instance {
|
||||||
@@ -52,44 +40,36 @@ impl Drop for UW8Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct UW8WatchDog {
|
struct UW8WatchDog {
|
||||||
interupt: wasmtime::InterruptHandle,
|
engine: Engine,
|
||||||
timeout: u32,
|
|
||||||
stop: bool,
|
stop: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MicroW8 {
|
impl MicroW8 {
|
||||||
pub fn new() -> Result<MicroW8> {
|
pub fn new(timeout: Option<u32>, window_config: WindowConfig) -> Result<MicroW8> {
|
||||||
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
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 =
|
let loader_module =
|
||||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
||||||
|
|
||||||
let options = WindowOptions {
|
let window = Window::new(window_config)?;
|
||||||
scale: minifb::Scale::X2,
|
|
||||||
scale_mode: minifb::ScaleMode::AspectRatioStretch,
|
|
||||||
resize: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let window = Window::new("MicroW8", 320, 240, options)?;
|
|
||||||
|
|
||||||
Ok(MicroW8 {
|
Ok(MicroW8 {
|
||||||
|
window,
|
||||||
|
stream: None,
|
||||||
engine,
|
engine,
|
||||||
loader_module,
|
loader_module,
|
||||||
window,
|
|
||||||
window_buffer: vec![0u32; 320 * 240],
|
|
||||||
instance: None,
|
|
||||||
timeout: 30,
|
|
||||||
disable_audio: false,
|
disable_audio: false,
|
||||||
|
module_data: None,
|
||||||
|
timeout: timeout.unwrap_or(0),
|
||||||
|
instance: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
|
||||||
self.instance = None;
|
|
||||||
for v in &mut self.window_buffer {
|
|
||||||
*v = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_audio(&mut self) {
|
pub fn disable_audio(&mut self) {
|
||||||
self.disable_audio = true;
|
self.disable_audio = true;
|
||||||
}
|
}
|
||||||
@@ -97,25 +77,23 @@ impl MicroW8 {
|
|||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
fn is_open(&self) -> bool {
|
fn is_open(&self) -> bool {
|
||||||
self.window.is_open() && !self.window.is_key_down(Key::Escape)
|
self.window.is_open()
|
||||||
}
|
|
||||||
|
|
||||||
fn set_timeout(&mut self, timeout: u32) {
|
|
||||||
self.timeout = timeout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||||
self.reset();
|
self.stream = None;
|
||||||
|
self.instance = None;
|
||||||
|
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
let mut store = wasmtime::Store::new(&self.engine, ());
|
||||||
|
store.set_epoch_deadline(60);
|
||||||
|
|
||||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||||
linker.define("env", "memory", memory)?;
|
linker.define(&store, "env", "memory", memory)?;
|
||||||
|
|
||||||
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
||||||
let load_uw8 = loader_instance.get_typed_func::<i32, i32, _>(&mut store, "load_uw8")?;
|
let load_uw8 = loader_instance.get_typed_func::<i32, i32>(&mut store, "load_uw8")?;
|
||||||
|
|
||||||
let platform_data = include_bytes!("../platform/bin/platform.uw8");
|
let platform_data = include_bytes!("../platform/bin/platform.uw8");
|
||||||
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data);
|
||||||
@@ -133,8 +111,7 @@ impl super::Runtime for MicroW8 {
|
|||||||
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
||||||
|
|
||||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||||
interupt: store.interrupt_handle()?,
|
engine: self.engine.clone(),
|
||||||
timeout: self.timeout,
|
|
||||||
stop: false,
|
stop: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -142,16 +119,11 @@ impl super::Runtime for MicroW8 {
|
|||||||
let watchdog = watchdog.clone();
|
let watchdog = watchdog.clone();
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
thread::sleep(Duration::from_millis(17));
|
thread::sleep(Duration::from_millis(17));
|
||||||
if let Ok(mut watchdog) = watchdog.lock() {
|
if let Ok(watchdog) = watchdog.lock() {
|
||||||
if watchdog.stop {
|
if watchdog.stop {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if watchdog.timeout > 0 {
|
watchdog.engine.increment_epoch();
|
||||||
watchdog.timeout -= 1;
|
|
||||||
if watchdog.timeout == 0 {
|
|
||||||
watchdog.interupt.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -159,23 +131,24 @@ impl super::Runtime for MicroW8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let instance = linker.instantiate(&mut store, &module)?;
|
let instance = linker.instantiate(&mut store, &module)?;
|
||||||
if let Ok(mut watchdog) = watchdog.lock() {
|
let end_frame = platform_instance.get_typed_func::<(), ()>(&mut store, "endFrame")?;
|
||||||
watchdog.timeout = 0;
|
let update = instance.get_typed_func::<(), ()>(&mut store, "upd").ok();
|
||||||
}
|
|
||||||
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
|
||||||
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
|
||||||
|
|
||||||
let sound = if self.disable_audio {
|
if let Some(start) = instance.get_typed_func::<(), ()>(&mut store, "start").ok() {
|
||||||
None
|
start.call(&mut store, ())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sound_tx, stream) = if self.disable_audio {
|
||||||
|
(None, None)
|
||||||
} else {
|
} else {
|
||||||
match init_sound(&self.engine, &platform_module, &module) {
|
match init_sound(&self.engine, &platform_module, &module) {
|
||||||
Ok(sound) => {
|
Ok(sound) => {
|
||||||
sound.stream.play()?;
|
sound.stream.play()?;
|
||||||
Some(sound)
|
(Some(sound.tx), Some(sound.stream))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to init sound: {}", err);
|
eprintln!("Failed to init sound: {}", err);
|
||||||
None
|
(None, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -186,57 +159,45 @@ impl super::Runtime for MicroW8 {
|
|||||||
end_frame,
|
end_frame,
|
||||||
update,
|
update,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
next_frame: Instant::now(),
|
|
||||||
module: module_data.into(),
|
|
||||||
watchdog,
|
watchdog,
|
||||||
sound,
|
sound_tx,
|
||||||
});
|
});
|
||||||
|
self.stream = stream;
|
||||||
|
self.module_data = Some(module_data.into());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_frame(&mut self) -> Result<()> {
|
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(());
|
let mut result = Ok(());
|
||||||
if let Some(mut instance) = self.instance.take() {
|
if let Some(mut instance) = self.instance.take() {
|
||||||
{
|
|
||||||
if let Some(sleep) = instance.next_frame.checked_duration_since(Instant::now()) {
|
|
||||||
std::thread::sleep(sleep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
let time = (now - instance.start_time).as_millis() as i32;
|
let time = (now - instance.start_time).as_millis() as i32;
|
||||||
{
|
let next_frame = {
|
||||||
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
|
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
|
||||||
instance.next_frame = now + Duration::from_millis((16 - offset) as u64);
|
let max = now + Duration::from_millis(17);
|
||||||
}
|
let next_center = now + Duration::from_millis((16 - offset) as u64);
|
||||||
|
next_center.min(max)
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut gamepad: u32 = 0;
|
|
||||||
for key in self.window.get_keys() {
|
|
||||||
if let Some(index) = GAMEPAD_KEYS
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, &k)| k == key)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
{
|
|
||||||
gamepad |= 1 << index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem = instance.memory.data_mut(&mut instance.store);
|
let mem = instance.memory.data_mut(&mut instance.store);
|
||||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||||
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
|
mem[68..72].copy_from_slice(&input.gamepads);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
instance.store.set_epoch_deadline(self.timeout as u64);
|
||||||
watchdog.timeout = self.timeout;
|
|
||||||
}
|
|
||||||
if let Some(ref update) = instance.update {
|
if let Some(ref update) = instance.update {
|
||||||
result = update.call(&mut instance.store, ());
|
if let Err(err) = update.call(&mut instance.store, ()) {
|
||||||
}
|
result = Err(err);
|
||||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
}
|
||||||
watchdog.timeout = 0;
|
|
||||||
}
|
}
|
||||||
instance.end_frame.call(&mut instance.store, ())?;
|
instance.end_frame.call(&mut instance.store, ())?;
|
||||||
|
|
||||||
@@ -244,33 +205,23 @@ impl super::Runtime for MicroW8 {
|
|||||||
|
|
||||||
let mut sound_regs = [0u8; 32];
|
let mut sound_regs = [0u8; 32];
|
||||||
sound_regs.copy_from_slice(&memory[80..112]);
|
sound_regs.copy_from_slice(&memory[80..112]);
|
||||||
if let Some(ref sound) = instance.sound {
|
if let Some(ref sound_tx) = instance.sound_tx {
|
||||||
sound.tx.send(RegisterUpdate {
|
let _ = sound_tx.send(RegisterUpdate {
|
||||||
time,
|
time,
|
||||||
data: sound_regs,
|
data: sound_regs,
|
||||||
})?;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let framebuffer = &memory[120..(120 + 320 * 240)];
|
let framebuffer_mem = &memory[120..(120 + 320 * 240)];
|
||||||
let palette = &memory[0x13000..];
|
let palette_mem = &memory[0x13000..];
|
||||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
self.window
|
||||||
let offset = color_index as usize * 4;
|
.end_frame(framebuffer_mem, palette_mem, next_frame);
|
||||||
self.window_buffer[i] = 0xff000000
|
|
||||||
| ((palette[offset] as u32) << 16)
|
|
||||||
| ((palette[offset + 1] as u32) << 8)
|
|
||||||
| palette[offset + 2] as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) {
|
if result.is_ok() {
|
||||||
self.load(&instance.module)?;
|
|
||||||
} else if result.is_ok() {
|
|
||||||
self.instance = Some(instance);
|
self.instance = Some(instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window
|
|
||||||
.update_with_buffer(&self.window_buffer, 320, 240)?;
|
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -304,15 +255,12 @@ fn add_native_functions(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
linker.define(
|
let global = wasmtime::Global::new(
|
||||||
"env",
|
&mut *store,
|
||||||
&format!("g_reserved{}", i),
|
GlobalType::new(ValType::I32, Mutability::Const),
|
||||||
wasmtime::Global::new(
|
0.into(),
|
||||||
&mut *store,
|
|
||||||
GlobalType::new(ValType::I32, Mutability::Const),
|
|
||||||
0.into(),
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
|
linker.define(&store, "env", &format!("g_reserved{}", i), global)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -325,14 +273,18 @@ fn instantiate_platform(
|
|||||||
) -> Result<wasmtime::Instance> {
|
) -> Result<wasmtime::Instance> {
|
||||||
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
||||||
|
|
||||||
for export in platform_instance.exports(&mut *store) {
|
let exports: Vec<(String, Func)> = platform_instance
|
||||||
linker.define(
|
.exports(&mut *store)
|
||||||
"env",
|
.map(|e| {
|
||||||
export.name(),
|
(
|
||||||
export
|
e.name().to_owned(),
|
||||||
.into_func()
|
e.into_func()
|
||||||
.expect("platform surely only exports functions"),
|
.expect("platform surely only exports functions"),
|
||||||
)?;
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for (name, func) in exports {
|
||||||
|
linker.define(&store, "env", &name, func)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(platform_instance)
|
Ok(platform_instance)
|
||||||
@@ -354,19 +306,20 @@ fn init_sound(
|
|||||||
module: &wasmtime::Module,
|
module: &wasmtime::Module,
|
||||||
) -> Result<Uw8Sound> {
|
) -> Result<Uw8Sound> {
|
||||||
let mut store = wasmtime::Store::new(engine, ());
|
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 memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
let mut linker = wasmtime::Linker::new(engine);
|
let mut linker = wasmtime::Linker::new(engine);
|
||||||
linker.define("env", "memory", memory)?;
|
linker.define(&store, "env", "memory", memory)?;
|
||||||
add_native_functions(&mut linker, &mut store)?;
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
|
|
||||||
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
||||||
let instance = linker.instantiate(&mut store, module)?;
|
let instance = linker.instantiate(&mut store, module)?;
|
||||||
|
|
||||||
let snd = instance
|
let snd = instance
|
||||||
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
|
.get_typed_func::<(i32,), f32>(&mut store, "snd")
|
||||||
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd"))?;
|
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32>(&mut store, "sndGes"))?;
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device = host
|
let device = host
|
||||||
@@ -375,33 +328,37 @@ fn init_sound(
|
|||||||
let mut configs: Vec<_> = device
|
let mut configs: Vec<_> = device
|
||||||
.supported_output_configs()?
|
.supported_output_configs()?
|
||||||
.filter(|config| {
|
.filter(|config| {
|
||||||
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
|
config.channels() == 2
|
||||||
|
&& (config.sample_format() == cpal::SampleFormat::F32
|
||||||
|
|| config.sample_format() == cpal::SampleFormat::I16)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
configs.sort_by_key(|config| {
|
configs.sort_by_key(|config| {
|
||||||
let rate = 44100
|
let rate = 44100
|
||||||
.max(config.min_sample_rate().0)
|
.max(config.min_sample_rate().0)
|
||||||
.min(config.max_sample_rate().0);
|
.min(config.max_sample_rate().0);
|
||||||
if rate >= 44100 {
|
let prio = if rate >= 44100 {
|
||||||
rate - 44100
|
rate - 44100
|
||||||
} else {
|
} else {
|
||||||
(44100 - rate) * 1000
|
(44100 - rate) * 1000
|
||||||
}
|
};
|
||||||
|
prio + (config.sample_format() == cpal::SampleFormat::I16) as u32
|
||||||
});
|
});
|
||||||
let config = configs
|
let config = configs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("Could not find float output config"))?;
|
.ok_or_else(|| anyhow!("Could not find float or 16bit signed output config"))?;
|
||||||
let sample_rate = cpal::SampleRate(44100)
|
let sample_rate = cpal::SampleRate(44100)
|
||||||
.max(config.min_sample_rate())
|
.max(config.min_sample_rate())
|
||||||
.max(config.max_sample_rate());
|
.min(config.max_sample_rate());
|
||||||
let config = config.with_sample_rate(sample_rate);
|
let config = config.with_sample_rate(sample_rate);
|
||||||
let buffer_size = match *config.buffer_size() {
|
let buffer_size = match *config.buffer_size() {
|
||||||
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
||||||
cpal::SupportedBufferSize::Range { min, max } => {
|
cpal::SupportedBufferSize::Range { min, max } => {
|
||||||
cpal::BufferSize::Fixed(65536.max(min).min(max))
|
cpal::BufferSize::Fixed(256.max(min).min(max))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let sample_format = config.sample_format();
|
||||||
let config = cpal::StreamConfig {
|
let config = cpal::StreamConfig {
|
||||||
buffer_size,
|
buffer_size,
|
||||||
..config.config()
|
..config.config()
|
||||||
@@ -421,8 +378,8 @@ fn init_sound(
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
||||||
let input_buffers = rs.input_buffer_allocate();
|
let input_buffers = rs.input_buffer_allocate(true);
|
||||||
let output_buffers = rs.output_buffer_allocate();
|
let output_buffers = rs.output_buffer_allocate(true);
|
||||||
Some(Resampler {
|
Some(Resampler {
|
||||||
resampler: rs,
|
resampler: rs,
|
||||||
input_buffers,
|
input_buffers,
|
||||||
@@ -434,95 +391,130 @@ fn init_sound(
|
|||||||
let mut sample_index = 0;
|
let mut sample_index = 0;
|
||||||
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
|
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
|
||||||
let mut current_time = 0;
|
let mut current_time = 0;
|
||||||
let stream = device.build_output_stream(
|
|
||||||
&config,
|
let mut callback = move |mut outer_buffer: &mut [f32], _: &_| {
|
||||||
move |mut outer_buffer: &mut [f32], _| {
|
let mut first_update = true;
|
||||||
let mut first_update = true;
|
while let Ok(update) = rx.try_recv() {
|
||||||
while let Ok(update) = rx.try_recv() {
|
if first_update {
|
||||||
if first_update {
|
current_time += update.time.wrapping_sub(current_time) / 8;
|
||||||
current_time += update.time.wrapping_sub(current_time) / 8;
|
first_update = false;
|
||||||
first_update = false;
|
}
|
||||||
}
|
pending_updates.push(update);
|
||||||
pending_updates.push(update);
|
}
|
||||||
|
|
||||||
|
while !outer_buffer.is_empty() {
|
||||||
|
store.set_epoch_deadline(30);
|
||||||
|
while pending_updates
|
||||||
|
.first()
|
||||||
|
.into_iter()
|
||||||
|
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
||||||
|
{
|
||||||
|
let update = pending_updates.remove(0);
|
||||||
|
memory.write(&mut store, 80, &update.data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
while !outer_buffer.is_empty() {
|
let duration = if let Some(update) = pending_updates.first() {
|
||||||
while pending_updates
|
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
||||||
.first()
|
} else {
|
||||||
.into_iter()
|
outer_buffer.len()
|
||||||
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
};
|
||||||
{
|
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
||||||
let update = pending_updates.remove(0);
|
|
||||||
memory.write(&mut store, 80, &update.data).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let duration = if let Some(update) = pending_updates.first() {
|
let mut buffer = &mut outer_buffer[..step_size];
|
||||||
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
|
||||||
|
{
|
||||||
|
let mem = memory.data_mut(&mut store);
|
||||||
|
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp_sample(s: f32) -> f32 {
|
||||||
|
if s.is_nan() {
|
||||||
|
0.0
|
||||||
} else {
|
} else {
|
||||||
outer_buffer.len()
|
s.max(-1.0).min(1.0)
|
||||||
};
|
|
||||||
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
|
||||||
|
|
||||||
let mut buffer = &mut outer_buffer[..step_size];
|
|
||||||
|
|
||||||
{
|
|
||||||
let mem = memory.data_mut(&mut store);
|
|
||||||
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref mut resampler) = resampler {
|
if let Some(ref mut resampler) = resampler {
|
||||||
while !buffer.is_empty() {
|
while !buffer.is_empty() {
|
||||||
let copy_size = resampler.output_buffers[0]
|
let copy_size = resampler.output_buffers[0]
|
||||||
.len()
|
.len()
|
||||||
.saturating_sub(resampler.output_index)
|
.saturating_sub(resampler.output_index)
|
||||||
.min(buffer.len() / 2);
|
.min(buffer.len() / 2);
|
||||||
if copy_size == 0 {
|
if copy_size == 0 {
|
||||||
resampler.input_buffers[0].clear();
|
resampler.input_buffers[0].clear();
|
||||||
resampler.input_buffers[1].clear();
|
resampler.input_buffers[1].clear();
|
||||||
for _ in 0..resampler.resampler.input_frames_next() {
|
for _ in 0..resampler.resampler.input_frames_next() {
|
||||||
resampler.input_buffers[0]
|
resampler.input_buffers[0].push(clamp_sample(
|
||||||
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
snd.call(&mut store, (sample_index,)).unwrap_or(0.0),
|
||||||
resampler.input_buffers[1]
|
));
|
||||||
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
|
resampler.input_buffers[1].push(clamp_sample(
|
||||||
sample_index = sample_index.wrapping_add(2);
|
snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0),
|
||||||
}
|
));
|
||||||
|
sample_index = sample_index.wrapping_add(2);
|
||||||
resampler
|
|
||||||
.resampler
|
|
||||||
.process_into_buffer(
|
|
||||||
&resampler.input_buffers,
|
|
||||||
&mut resampler.output_buffers,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
resampler.output_index = 0;
|
|
||||||
} else {
|
|
||||||
for i in 0..copy_size {
|
|
||||||
buffer[i * 2] =
|
|
||||||
resampler.output_buffers[0][resampler.output_index + i];
|
|
||||||
buffer[i * 2 + 1] =
|
|
||||||
resampler.output_buffers[1][resampler.output_index + i];
|
|
||||||
}
|
|
||||||
resampler.output_index += copy_size;
|
|
||||||
buffer = &mut buffer[copy_size * 2..];
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
resampler
|
||||||
for v in buffer {
|
.resampler
|
||||||
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
.process_into_buffer(
|
||||||
sample_index = sample_index.wrapping_add(1);
|
&resampler.input_buffers,
|
||||||
|
&mut resampler.output_buffers,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
resampler.output_index = 0;
|
||||||
|
} else {
|
||||||
|
for i in 0..copy_size {
|
||||||
|
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i];
|
||||||
|
buffer[i * 2 + 1] =
|
||||||
|
resampler.output_buffers[1][resampler.output_index + i];
|
||||||
|
}
|
||||||
|
resampler.output_index += copy_size;
|
||||||
|
buffer = &mut buffer[copy_size * 2..];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
outer_buffer = &mut outer_buffer[step_size..];
|
for v in buffer {
|
||||||
current_time =
|
*v = clamp_sample(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||||
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
sample_index = sample_index.wrapping_add(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
move |err| {
|
outer_buffer = &mut outer_buffer[step_size..];
|
||||||
dbg!(err);
|
current_time = current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
||||||
},
|
}
|
||||||
)?;
|
};
|
||||||
|
let stream = if sample_format == cpal::SampleFormat::F32 {
|
||||||
|
device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
callback,
|
||||||
|
move |err| {
|
||||||
|
dbg!(err);
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
move |mut buffer: &mut [i16], info| {
|
||||||
|
let mut float_buffer = [0f32; 256];
|
||||||
|
|
||||||
|
while !buffer.is_empty() {
|
||||||
|
let step_size = buffer.len().min(float_buffer.len());
|
||||||
|
let step_buffer = &mut float_buffer[..step_size];
|
||||||
|
callback(step_buffer, info);
|
||||||
|
for (dest, src) in buffer.iter_mut().take(step_size).zip(step_buffer.iter()) {
|
||||||
|
*dest = (src.max(-1.0).min(1.0) * 32767.0) as i16;
|
||||||
|
}
|
||||||
|
buffer = &mut buffer[step_size..];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
dbg!(err);
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Uw8Sound { stream, tx })
|
Ok(Uw8Sound { stream, tx })
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -11,6 +11,7 @@ use warp::{http::Response, Filter};
|
|||||||
pub struct RunWebServer {
|
pub struct RunWebServer {
|
||||||
cart: Arc<Mutex<Vec<u8>>>,
|
cart: Arc<Mutex<Vec<u8>>>,
|
||||||
tx: broadcast::Sender<()>,
|
tx: broadcast::Sender<()>,
|
||||||
|
socket_addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunWebServer {
|
impl RunWebServer {
|
||||||
@@ -18,8 +19,13 @@ impl RunWebServer {
|
|||||||
let cart = Arc::new(Mutex::new(Vec::new()));
|
let cart = Arc::new(Mutex::new(Vec::new()));
|
||||||
let (tx, _) = broadcast::channel(1);
|
let (tx, _) = broadcast::channel(1);
|
||||||
|
|
||||||
|
let socket_addr = "127.0.0.1:3030"
|
||||||
|
.parse::<SocketAddr>()
|
||||||
|
.expect("Failed to parse socket address");
|
||||||
|
|
||||||
let server_cart = cart.clone();
|
let server_cart = cart.clone();
|
||||||
let server_tx = tx.clone();
|
let server_tx = tx.clone();
|
||||||
|
let server_addr = socket_addr.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_io()
|
.enable_io()
|
||||||
@@ -47,24 +53,26 @@ impl RunWebServer {
|
|||||||
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let socket_addr = "127.0.0.1:3030"
|
let server_future = warp::serve(html.or(cart).or(events)).bind(server_addr);
|
||||||
.parse::<SocketAddr>()
|
|
||||||
.expect("Failed to parse socket address");
|
|
||||||
|
|
||||||
let server_future = warp::serve(html.or(cart).or(events)).bind(socket_addr);
|
|
||||||
println!("Point browser at http://{}", socket_addr);
|
|
||||||
let _ignore_result = webbrowser::open(&format!("http://{}", socket_addr));
|
|
||||||
server_future.await
|
server_future.await
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
RunWebServer { cart, tx }
|
RunWebServer {
|
||||||
|
cart,
|
||||||
|
tx,
|
||||||
|
socket_addr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for RunWebServer {
|
impl super::Runtime for RunWebServer {
|
||||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||||
if let Ok(mut lock) = self.cart.lock() {
|
if let Ok(mut lock) = self.cart.lock() {
|
||||||
|
if lock.is_empty() && !module_data.is_empty() {
|
||||||
|
println!("Point browser at http://{}", self.socket_addr);
|
||||||
|
let _ignore_result = webbrowser::open(&format!("http://{}", self.socket_addr));
|
||||||
|
}
|
||||||
lock.clear();
|
lock.clear();
|
||||||
lock.extend_from_slice(module_data);
|
lock.extend_from_slice(module_data);
|
||||||
}
|
}
|
||||||
@@ -86,4 +94,4 @@ impl Default for RunWebServer {
|
|||||||
fn default() -> RunWebServer {
|
fn default() -> RunWebServer {
|
||||||
RunWebServer::new()
|
RunWebServer::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import "env.memory" memory(4);
|
|
||||||
import "env.printString" fn print(i32);
|
|
||||||
|
|
||||||
export fn upd() {
|
|
||||||
}
|
|
||||||
|
|
||||||
start fn start() {
|
|
||||||
print(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
data 0 {
|
|
||||||
"Press " i8(0xe0) " and " i8(0xe1) " to adjust, " i8(0xcc) " to commit." i8(0)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+3
-2
@@ -2,17 +2,18 @@ import "env.memory" memory(4);
|
|||||||
import "env.pow" fn pow(f32, f32) -> f32;
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
import "env.cls" fn cls(i32);
|
import "env.cls" fn cls(i32);
|
||||||
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||||
|
|
||||||
include "../platform/src/ges.cwa"
|
include "../platform/src/ges.cwa"
|
||||||
|
|
||||||
export fn snd(t: i32) -> f32 {
|
export fn snd(t: i32) -> f32 {
|
||||||
gesSnd(t)
|
sndGes(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
80?0 = 32!32 / 200 & 2 | 0x41;
|
80?0 = 32!32 / 200 & 2 | 0x41;
|
||||||
80?3 = (32!32 / 400)%7*12/7 + 40;
|
80?3 = (32!32 / 400)%8*12/7 + 40;
|
||||||
let pulse = (32!32 * 256 / 2000) & 511;
|
let pulse = (32!32 * 256 / 2000) & 511;
|
||||||
if pulse >= 256 {
|
if pulse >= 256 {
|
||||||
pulse = 511 - pulse;
|
pulse = 511 - pulse;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
include "../examples/include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn start() {
|
||||||
|
printChar('Test');
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
Generated
+124
-12
@@ -56,6 +56,21 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -71,6 +86,26 @@ version = "2.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -83,6 +118,12 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lexopt"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.112"
|
version = "0.2.112"
|
||||||
@@ -120,10 +161,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "percent-encoding"
|
||||||
version = "0.4.2"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
@@ -163,6 +210,26 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@@ -174,6 +241,36 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -188,13 +285,24 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upkr"
|
name = "upkr"
|
||||||
version = "0.1.0"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/exoticorn/upkr.git?rev=2e7983fc#2e7983fc650788d98da2eecef2d16f63e849e4a0"
|
source = "git+https://github.com/exoticorn/upkr.git?rev=080db40d0088bbee2bdf3c5c75288ac7853d6b7a#080db40d0088bbee2bdf3c5c75288ac7853d6b7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cdivsufsort",
|
"cdivsufsort",
|
||||||
"pbr",
|
"lexopt",
|
||||||
"pico-args",
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -207,7 +315,7 @@ dependencies = [
|
|||||||
"upkr",
|
"upkr",
|
||||||
"walrus",
|
"walrus",
|
||||||
"wasm-encoder",
|
"wasm-encoder",
|
||||||
"wasmparser 0.81.0",
|
"wasmparser 0.99.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -244,9 +352,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.8.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
@@ -259,9 +367,13 @@ checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.81.0"
|
version = "0.99.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
checksum = "9ef3b717afc67f848f412d4f02c127dd3e35a0eecd58c684580414df4fde01d3"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
|
|||||||
+4
-4
@@ -6,10 +6,10 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmparser = "0.81"
|
wasmparser = "0.99"
|
||||||
wasm-encoder = "0.8"
|
wasm-encoder = "0.22"
|
||||||
walrus = "0.19"
|
walrus = "0.19"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
pico-args = "0.4"
|
pico-args = "0.5"
|
||||||
upkr = { git = "https://github.com/exoticorn/upkr.git", rev = "2e7983fc" }
|
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::*;
|
||||||
@@ -167,6 +167,7 @@ impl BaseModule {
|
|||||||
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
|
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
||||||
|
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
|
||||||
|
|
||||||
for i in functions.len()..64 {
|
for i in functions.len()..64 {
|
||||||
add_function(
|
add_function(
|
||||||
@@ -217,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,
|
||||||
@@ -233,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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,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);
|
||||||
@@ -286,7 +288,7 @@ impl BaseModule {
|
|||||||
|
|
||||||
pub fn create_binary(path: &Path) -> Result<()> {
|
pub fn create_binary(path: &Path) -> Result<()> {
|
||||||
let base1 = BaseModule::for_format_version(1)?.to_wasm();
|
let base1 = BaseModule::for_format_version(1)?.to_wasm();
|
||||||
let data = upkr::pack(&base1, 4, 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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
use std::path::Path;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
||||||
let mut module = walrus::Module::from_file(in_path)?;
|
let mut module = walrus::Module::from_file(in_path)?;
|
||||||
|
|
||||||
let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() {
|
let exports_to_delete: Vec<_> = module
|
||||||
"upd" => None,
|
.exports
|
||||||
_ => Some(export.id())
|
.iter()
|
||||||
}).collect();
|
.filter_map(|export| match export.name.as_str() {
|
||||||
|
"start" | "upd" | "snd" => None,
|
||||||
|
_ => Some(export.id()),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
for id in exports_to_delete {
|
for id in exports_to_delete {
|
||||||
module.exports.delete(id);
|
module.exports.delete(id);
|
||||||
@@ -18,4 +22,4 @@ pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
|||||||
module.emit_wasm_file(out_path)?;
|
module.emit_wasm_file(out_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -1,7 +1,15 @@
|
|||||||
mod base_module;
|
mod base_module;
|
||||||
mod pack;
|
|
||||||
mod filter_exports;
|
mod filter_exports;
|
||||||
|
mod pack;
|
||||||
|
|
||||||
pub use base_module::BaseModule;
|
pub use base_module::BaseModule;
|
||||||
pub use pack::{pack, pack_file, unpack, unpack_file, PackConfig};
|
|
||||||
pub use filter_exports::filter_exports;
|
pub use filter_exports::filter_exports;
|
||||||
|
pub use pack::{pack, pack_file, unpack, unpack_file, PackConfig};
|
||||||
|
|
||||||
|
pub fn compressed_size(cart: &[u8]) -> f32 {
|
||||||
|
if cart[0] != 2 {
|
||||||
|
cart.len() as f32
|
||||||
|
} else {
|
||||||
|
upkr::compressed_size(&cart[1..]) + 1.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+364
-120
@@ -10,7 +10,7 @@ use std::{
|
|||||||
use wasm_encoder as enc;
|
use wasm_encoder as enc;
|
||||||
use wasmparser::{
|
use wasmparser::{
|
||||||
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
|
||||||
ImportSectionEntryType, ImportSectionReader, TableSectionReader, TypeSectionReader,
|
ImportSectionReader, TableSectionReader, TypeRef, TypeSectionReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PackConfig {
|
pub struct PackConfig {
|
||||||
@@ -63,6 +63,7 @@ pub fn pack(data: &[u8], config: &PackConfig) -> Result<Vec<u8>> {
|
|||||||
uw8.extend_from_slice(&upkr::pack(
|
uw8.extend_from_slice(&upkr::pack(
|
||||||
&result[8..],
|
&result[8..],
|
||||||
level,
|
level,
|
||||||
|
&upkr::Config::default(),
|
||||||
Some(&mut |pos| {
|
Some(&mut |pos| {
|
||||||
pb.set(pos as u64);
|
pb.set(pos as u64);
|
||||||
}),
|
}),
|
||||||
@@ -89,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),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,18 +136,19 @@ 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,
|
||||||
F32 => ValType::F32,
|
F32 => ValType::F32,
|
||||||
F64 => ValType::F64,
|
F64 => ValType::F64,
|
||||||
|
V128 => ValType::V128,
|
||||||
_ => bail!("Type {:?} isn't a value type", type_),
|
_ => bail!("Type {:?} isn't a value type", type_),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +206,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)?));
|
||||||
@@ -219,17 +224,22 @@ impl<'a> ParsedModule<'a> {
|
|||||||
validate_table_section(reader)?;
|
validate_table_section(reader)?;
|
||||||
table_section = Some(Section::new(range, ()));
|
table_section = Some(Section::new(range, ()));
|
||||||
}
|
}
|
||||||
Payload::ElementSection(mut reader) => {
|
Payload::MemorySection(reader) => {
|
||||||
let mut elements = Vec::with_capacity(reader.get_count() as usize);
|
if reader.count() != 0 {
|
||||||
for _ in 0..reader.get_count() {
|
bail!("Found non-empty MemorySection. Memory has to be imported!");
|
||||||
elements.push(Element::parse(reader.read()?)?);
|
}
|
||||||
|
}
|
||||||
|
Payload::ElementSection(reader) => {
|
||||||
|
let mut elements = Vec::with_capacity(reader.count() as usize);
|
||||||
|
for element in reader {
|
||||||
|
elements.push(Element::parse(element?)?);
|
||||||
}
|
}
|
||||||
element_section = Some(elements);
|
element_section = Some(elements);
|
||||||
}
|
}
|
||||||
Payload::CodeSectionStart { .. } => (),
|
Payload::CodeSectionStart { .. } => (),
|
||||||
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
Payload::CodeSectionEntry(body) => function_bodies.push(body),
|
||||||
Payload::CustomSection { .. } => (),
|
Payload::CustomSection { .. } => (),
|
||||||
Payload::End => break,
|
Payload::End(..) => break,
|
||||||
other => bail!("Unsupported section: {:?}", other),
|
other => bail!("Unsupported section: {:?}", other),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +467,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);
|
||||||
}
|
}
|
||||||
@@ -480,7 +490,7 @@ impl<'a> ParsedModule<'a> {
|
|||||||
}
|
}
|
||||||
element_section.active(
|
element_section.active(
|
||||||
None,
|
None,
|
||||||
&wasm_encoder::Instruction::I32Const(element.start_index as i32),
|
&wasm_encoder::ConstExpr::i32_const(element.start_index as i32),
|
||||||
ValType::FuncRef,
|
ValType::FuncRef,
|
||||||
wasm_encoder::Elements::Functions(&functions),
|
wasm_encoder::Elements::Functions(&functions),
|
||||||
);
|
);
|
||||||
@@ -529,28 +539,27 @@ fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::Funct
|
|||||||
|
|
||||||
for type_def in reader {
|
for type_def in reader {
|
||||||
match type_def? {
|
match type_def? {
|
||||||
wasmparser::TypeDef::Func(fnc) => {
|
wasmparser::Type::Func(fnc) => {
|
||||||
if fnc.returns.len() > 1 {
|
if fnc.results().len() > 1 {
|
||||||
bail!("Multi-value not supported");
|
bail!("Multi-value not supported");
|
||||||
}
|
}
|
||||||
let params = to_val_type_vec(&fnc.params)?;
|
let params = to_val_type_vec(fnc.params())?;
|
||||||
let result = to_val_type_vec(&fnc.returns)?.into_iter().next();
|
let result = to_val_type_vec(fnc.results())?.into_iter().next();
|
||||||
function_types.push(FunctionType { params, result });
|
function_types.push(FunctionType { params, result });
|
||||||
}
|
}
|
||||||
t => bail!("Unsupported type def {:?}", t),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(function_types)
|
Ok(function_types)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_table_section(mut reader: TableSectionReader) -> Result<()> {
|
fn validate_table_section(reader: TableSectionReader) -> Result<()> {
|
||||||
if reader.get_count() != 1 {
|
if reader.count() != 1 {
|
||||||
bail!("Only up to one table supported");
|
bail!("Only up to one table supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
let type_ = reader.read()?;
|
let type_ = reader.into_iter().next().unwrap()?;
|
||||||
if type_.element_type != wasmparser::Type::FuncRef {
|
if type_.element_type != wasmparser::ValType::FuncRef {
|
||||||
bail!("Only one funcref table is supported");
|
bail!("Only one funcref table is supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,45 +593,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,40 +644,37 @@ struct Element {
|
|||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
fn parse(element: wasmparser::Element) -> Result<Element> {
|
fn parse(element: wasmparser::Element) -> Result<Element> {
|
||||||
if element.ty != wasmparser::Type::FuncRef {
|
match element.items {
|
||||||
bail!("Table element type is not FuncRef");
|
wasmparser::ElementItems::Functions(funcs_reader) => {
|
||||||
}
|
let start_index = if let wasmparser::ElementKind::Active {
|
||||||
|
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 start_index = if let wasmparser::ElementKind::Active {
|
let mut functions = Vec::with_capacity(funcs_reader.count() as usize);
|
||||||
init_expr,
|
for index in funcs_reader {
|
||||||
table_index: 0,
|
functions.push(index?);
|
||||||
} = element.kind
|
}
|
||||||
{
|
|
||||||
let mut init_reader = init_expr.get_operators_reader();
|
Ok(Element {
|
||||||
if let wasmparser::Operator::I32Const { value: start_index } = init_reader.read()? {
|
start_index,
|
||||||
start_index as u32
|
functions,
|
||||||
} else {
|
})
|
||||||
bail!("Table element start index is not a integer constant");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("Unsupported table element kind");
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut items_reader = element.items.get_items_reader()?;
|
|
||||||
|
|
||||||
let mut functions = Vec::with_capacity(items_reader.get_count() as usize);
|
|
||||||
for _ in 0..items_reader.get_count() {
|
|
||||||
if let wasmparser::ElementItem::Func(index) = items_reader.read()? {
|
|
||||||
functions.push(index);
|
|
||||||
} else {
|
|
||||||
bail!("Table element item is not a function");
|
|
||||||
}
|
}
|
||||||
|
_ => bail!("Table element type is not FuncRef"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Element {
|
|
||||||
start_index,
|
|
||||||
functions,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,8 +705,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
|
||||||
}
|
}
|
||||||
@@ -728,13 +727,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))?,
|
||||||
@@ -748,7 +745,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,
|
||||||
@@ -763,24 +760,31 @@ fn remap_function(
|
|||||||
function.instruction(&match op? {
|
function.instruction(&match op? {
|
||||||
De::Unreachable => En::Unreachable,
|
De::Unreachable => En::Unreachable,
|
||||||
De::Nop => En::Nop,
|
De::Nop => En::Nop,
|
||||||
De::Block { ty } => En::Block(block_type(ty)?),
|
De::Block { blockty } => En::Block(block_type(blockty)?),
|
||||||
De::Loop { ty } => En::Loop(block_type(ty)?),
|
De::Loop { blockty } => En::Loop(block_type(blockty)?),
|
||||||
De::If { ty } => En::If(block_type(ty)?),
|
De::If { blockty } => En::If(block_type(blockty)?),
|
||||||
De::Else => En::Else,
|
De::Else => En::Else,
|
||||||
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
|
De::Try { .. } | De::Catch { .. } | De::Throw { .. } | De::Rethrow { .. } => todo!(),
|
||||||
De::End => En::End,
|
De::End => En::End,
|
||||||
De::Br { relative_depth } => En::Br(relative_depth),
|
De::Br { relative_depth } => En::Br(relative_depth),
|
||||||
De::BrIf { relative_depth } => En::BrIf(relative_depth),
|
De::BrIf { relative_depth } => En::BrIf(relative_depth),
|
||||||
De::BrTable { .. } => todo!(),
|
De::BrTable { targets } => En::BrTable(
|
||||||
|
targets.targets().collect::<Result<Vec<u32>, _>>()?.into(),
|
||||||
|
targets.default(),
|
||||||
|
),
|
||||||
De::Return => En::Return,
|
De::Return => En::Return,
|
||||||
De::Call { function_index } => En::Call(
|
De::Call { function_index } => En::Call(
|
||||||
*function_map
|
*function_map
|
||||||
.get(&function_index)
|
.get(&function_index)
|
||||||
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
|
||||||
),
|
),
|
||||||
De::CallIndirect { index, table_index } => En::CallIndirect {
|
De::CallIndirect {
|
||||||
|
type_index,
|
||||||
|
table_index,
|
||||||
|
table_byte: _, // what is this supposed to be?
|
||||||
|
} => En::CallIndirect {
|
||||||
ty: *type_map
|
ty: *type_map
|
||||||
.get(&index)
|
.get(&type_index)
|
||||||
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
|
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
|
||||||
table: table_index,
|
table: table_index,
|
||||||
},
|
},
|
||||||
@@ -800,16 +804,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)),
|
||||||
@@ -828,7 +832,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,
|
||||||
@@ -839,7 +843,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,
|
||||||
@@ -849,13 +853,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,
|
||||||
@@ -962,6 +966,246 @@ 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),
|
||||||
|
|
||||||
|
De::V128Const { value } => En::V128Const(value.i128()),
|
||||||
|
De::V128Load { memarg } => En::V128Load(mem(memarg)),
|
||||||
|
De::V128Store { memarg } => En::V128Store(mem(memarg)),
|
||||||
|
De::V128Load8x8S { memarg } => En::V128Load8x8S(mem(memarg)),
|
||||||
|
De::V128Load8x8U { memarg } => En::V128Load8x8U(mem(memarg)),
|
||||||
|
De::V128Load16x4S { memarg } => En::V128Load16x4S(mem(memarg)),
|
||||||
|
De::V128Load16x4U { memarg } => En::V128Load16x4U(mem(memarg)),
|
||||||
|
De::V128Load32x2S { memarg } => En::V128Load32x2S(mem(memarg)),
|
||||||
|
De::V128Load32x2U { memarg } => En::V128Load32x2U(mem(memarg)),
|
||||||
|
De::V128Load8Splat { memarg } => En::V128Load8Splat(mem(memarg)),
|
||||||
|
De::V128Load16Splat { memarg } => En::V128Load16Splat(mem(memarg)),
|
||||||
|
De::V128Load32Splat { memarg } => En::V128Load32Splat(mem(memarg)),
|
||||||
|
De::V128Load64Splat { memarg } => En::V128Load64Splat(mem(memarg)),
|
||||||
|
De::V128Load32Zero { memarg } => En::V128Load32Zero(mem(memarg)),
|
||||||
|
De::V128Load64Zero { memarg } => En::V128Load64Zero(mem(memarg)),
|
||||||
|
|
||||||
|
De::V128Load8Lane { memarg, lane } => En::V128Load8Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Load16Lane { memarg, lane } => En::V128Load16Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Load32Lane { memarg, lane } => En::V128Load32Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Load64Lane { memarg, lane } => En::V128Load64Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Store8Lane { memarg, lane } => En::V128Store8Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Store16Lane { memarg, lane } => En::V128Store16Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Store32Lane { memarg, lane } => En::V128Store32Lane { memarg: mem(memarg), lane },
|
||||||
|
De::V128Store64Lane { memarg, lane } => En::V128Store64Lane { memarg: mem(memarg), lane },
|
||||||
|
|
||||||
|
De::I8x16ExtractLaneS { lane } => En::I8x16ExtractLaneS(lane),
|
||||||
|
De::I8x16ExtractLaneU { lane } => En::I8x16ExtractLaneU(lane),
|
||||||
|
De::I8x16ReplaceLane { lane } => En::I8x16ReplaceLane(lane),
|
||||||
|
De::I16x8ExtractLaneS { lane } => En::I16x8ExtractLaneS(lane),
|
||||||
|
De::I16x8ExtractLaneU { lane } => En::I16x8ExtractLaneU(lane),
|
||||||
|
De::I16x8ReplaceLane { lane } => En::I16x8ReplaceLane(lane),
|
||||||
|
De::I32x4ExtractLane { lane } => En::I32x4ExtractLane(lane),
|
||||||
|
De::I32x4ReplaceLane { lane } => En::I32x4ReplaceLane(lane),
|
||||||
|
De::I64x2ExtractLane { lane } => En::I64x2ExtractLane(lane),
|
||||||
|
De::I64x2ReplaceLane { lane } => En::I64x2ReplaceLane(lane),
|
||||||
|
De::F32x4ExtractLane { lane } => En::F32x4ExtractLane(lane),
|
||||||
|
De::F32x4ReplaceLane { lane } => En::F32x4ReplaceLane(lane),
|
||||||
|
De::F64x2ExtractLane { lane } => En::F64x2ExtractLane(lane),
|
||||||
|
De::F64x2ReplaceLane { lane } => En::F64x2ReplaceLane(lane),
|
||||||
|
|
||||||
|
De::I8x16Splat => En::I8x16Splat,
|
||||||
|
De::I16x8Splat => En::I16x8Splat,
|
||||||
|
De::I32x4Splat => En::I32x4Splat,
|
||||||
|
De::I64x2Splat => En::I64x2Splat,
|
||||||
|
De::F32x4Splat => En::F32x4Splat,
|
||||||
|
De::F64x2Splat => En::F64x2Splat,
|
||||||
|
De::I8x16Swizzle => En::I8x16Swizzle,
|
||||||
|
De::I8x16Add => En::I8x16Add,
|
||||||
|
De::I16x8Add => En::I16x8Add,
|
||||||
|
De::I32x4Add => En::I32x4Add,
|
||||||
|
De::I64x2Add => En::I64x2Add,
|
||||||
|
De::F32x4Add => En::F32x4Add,
|
||||||
|
De::F64x2Add => En::F64x2Add,
|
||||||
|
De::I8x16Sub => En::I8x16Sub,
|
||||||
|
De::I16x8Sub => En::I16x8Sub,
|
||||||
|
De::I32x4Sub => En::I32x4Sub,
|
||||||
|
De::I64x2Sub => En::I64x2Sub,
|
||||||
|
De::F32x4Sub => En::F32x4Sub,
|
||||||
|
De::F64x2Sub => En::F64x2Sub,
|
||||||
|
De::I16x8Mul => En::I16x8Mul,
|
||||||
|
De::I32x4Mul => En::I32x4Mul,
|
||||||
|
De::I64x2Mul => En::I64x2Mul,
|
||||||
|
De::F32x4Mul => En::F32x4Mul,
|
||||||
|
De::F64x2Mul => En::F64x2Mul,
|
||||||
|
De::I32x4DotI16x8S => En::I32x4DotI16x8S,
|
||||||
|
De::I8x16Neg => En::I8x16Neg,
|
||||||
|
De::I16x8Neg => En::I16x8Neg,
|
||||||
|
De::I32x4Neg => En::I32x4Neg,
|
||||||
|
De::I64x2Neg => En::I64x2Neg,
|
||||||
|
De::F32x4Neg => En::F32x4Neg,
|
||||||
|
De::F64x2Neg => En::F64x2Neg,
|
||||||
|
De::I16x8ExtMulLowI8x16S => En::I16x8ExtMulLowI8x16S,
|
||||||
|
De::I16x8ExtMulHighI8x16S => En::I16x8ExtMulHighI8x16S,
|
||||||
|
De::I16x8ExtMulLowI8x16U => En::I16x8ExtMulLowI8x16U,
|
||||||
|
De::I16x8ExtMulHighI8x16U => En::I16x8ExtMulHighI8x16U,
|
||||||
|
De::I32x4ExtMulLowI16x8S => En::I32x4ExtMulLowI16x8S,
|
||||||
|
De::I32x4ExtMulHighI16x8S => En::I32x4ExtMulHighI16x8S,
|
||||||
|
De::I32x4ExtMulLowI16x8U => En::I32x4ExtMulLowI16x8U,
|
||||||
|
De::I32x4ExtMulHighI16x8U => En::I32x4ExtMulHighI16x8U,
|
||||||
|
De::I64x2ExtMulLowI32x4S => En::I64x2ExtMulLowI32x4S,
|
||||||
|
De::I64x2ExtMulHighI32x4S => En::I64x2ExtMulHighI32x4S,
|
||||||
|
De::I64x2ExtMulLowI32x4U => En::I64x2ExtMulLowI32x4U,
|
||||||
|
De::I64x2ExtMulHighI32x4U => En::I64x2ExtMulHighI32x4U,
|
||||||
|
De::I16x8ExtAddPairwiseI8x16S => En::I16x8ExtAddPairwiseI8x16S,
|
||||||
|
De::I16x8ExtAddPairwiseI8x16U => En::I16x8ExtAddPairwiseI8x16U,
|
||||||
|
De::I32x4ExtAddPairwiseI16x8S => En::I32x4ExtAddPairwiseI16x8S,
|
||||||
|
De::I32x4ExtAddPairwiseI16x8U => En::I32x4ExtAddPairwiseI16x8U,
|
||||||
|
De::I8x16AddSatS => En::I8x16AddSatS,
|
||||||
|
De::I8x16AddSatU => En::I8x16AddSatU,
|
||||||
|
De::I16x8AddSatS => En::I16x8AddSatS,
|
||||||
|
De::I16x8AddSatU => En::I16x8AddSatU,
|
||||||
|
De::I8x16SubSatS => En::I8x16SubSatS,
|
||||||
|
De::I8x16SubSatU => En::I8x16SubSatU,
|
||||||
|
De::I16x8SubSatS => En::I16x8SubSatS,
|
||||||
|
De::I16x8SubSatU => En::I16x8SubSatU,
|
||||||
|
De::I16x8Q15MulrSatS => En::I16x8Q15MulrSatS,
|
||||||
|
De::I8x16MinS => En::I8x16MinS,
|
||||||
|
De::I8x16MinU => En::I8x16MinU,
|
||||||
|
De::I16x8MinS => En::I16x8MinS,
|
||||||
|
De::I16x8MinU => En::I16x8MinU,
|
||||||
|
De::I32x4MinS => En::I32x4MinS,
|
||||||
|
De::I32x4MinU => En::I32x4MinU,
|
||||||
|
De::F32x4Min => En::F32x4Min,
|
||||||
|
De::F64x2Min => En::F64x2Min,
|
||||||
|
De::F32x4PMin => En::F32x4PMin,
|
||||||
|
De::F64x2PMin => En::F64x2PMin,
|
||||||
|
De::I8x16MaxS => En::I8x16MaxS,
|
||||||
|
De::I8x16MaxU => En::I8x16MaxU,
|
||||||
|
De::I16x8MaxS => En::I16x8MaxS,
|
||||||
|
De::I16x8MaxU => En::I16x8MaxU,
|
||||||
|
De::I32x4MaxS => En::I32x4MaxS,
|
||||||
|
De::I32x4MaxU => En::I32x4MaxU,
|
||||||
|
De::F32x4Max => En::F32x4Max,
|
||||||
|
De::F64x2Max => En::F64x2Max,
|
||||||
|
De::F32x4PMax => En::F32x4PMax,
|
||||||
|
De::F64x2PMax => En::F64x2PMax,
|
||||||
|
De::I8x16AvgrU => En::I8x16AvgrU,
|
||||||
|
De::I16x8AvgrU => En::I16x8AvgrU,
|
||||||
|
De::I8x16Abs => En::I8x16Abs,
|
||||||
|
De::I16x8Abs => En::I16x8Abs,
|
||||||
|
De::I32x4Abs => En::I32x4Abs,
|
||||||
|
De::I64x2Abs => En::I64x2Abs,
|
||||||
|
De::F32x4Abs => En::F32x4Abs,
|
||||||
|
De::F64x2Abs => En::F64x2Abs,
|
||||||
|
De::I8x16Shl => En::I8x16Shl,
|
||||||
|
De::I16x8Shl => En::I16x8Shl,
|
||||||
|
De::I32x4Shl => En::I32x4Shl,
|
||||||
|
De::I64x2Shl => En::I64x2Shl,
|
||||||
|
De::I8x16ShrS => En::I8x16ShrS,
|
||||||
|
De::I8x16ShrU => En::I8x16ShrU,
|
||||||
|
De::I16x8ShrS => En::I16x8ShrS,
|
||||||
|
De::I16x8ShrU => En::I16x8ShrU,
|
||||||
|
De::I32x4ShrS => En::I32x4ShrS,
|
||||||
|
De::I32x4ShrU => En::I32x4ShrU,
|
||||||
|
De::I64x2ShrS => En::I64x2ShrS,
|
||||||
|
De::I64x2ShrU => En::I64x2ShrU,
|
||||||
|
De::V128And => En::V128And,
|
||||||
|
De::V128Or => En::V128Or,
|
||||||
|
De::V128Xor => En::V128Xor,
|
||||||
|
De::V128Not => En::V128Not,
|
||||||
|
De::V128AndNot => En::V128AndNot,
|
||||||
|
De::V128Bitselect => En::V128Bitselect,
|
||||||
|
De::I8x16Popcnt => En::I8x16Popcnt,
|
||||||
|
De::V128AnyTrue => En::V128AnyTrue,
|
||||||
|
De::I8x16AllTrue => En::I8x16AllTrue,
|
||||||
|
De::I16x8AllTrue => En::I16x8AllTrue,
|
||||||
|
De::I32x4AllTrue => En::I32x4AllTrue,
|
||||||
|
De::I64x2AllTrue => En::I64x2AllTrue,
|
||||||
|
De::I8x16Bitmask => En::I8x16Bitmask,
|
||||||
|
De::I16x8Bitmask => En::I16x8Bitmask,
|
||||||
|
De::I32x4Bitmask => En::I32x4Bitmask,
|
||||||
|
De::I64x2Bitmask => En::I64x2Bitmask,
|
||||||
|
De::I8x16Eq => En::I8x16Eq,
|
||||||
|
De::I16x8Eq => En::I16x8Eq,
|
||||||
|
De::I32x4Eq => En::I32x4Eq,
|
||||||
|
De::I64x2Eq => En::I64x2Eq,
|
||||||
|
De::F32x4Eq => En::F32x4Eq,
|
||||||
|
De::F64x2Eq => En::F64x2Eq,
|
||||||
|
De::I8x16Ne => En::I8x16Ne,
|
||||||
|
De::I16x8Ne => En::I16x8Ne,
|
||||||
|
De::I32x4Ne => En::I32x4Ne,
|
||||||
|
De::I64x2Ne => En::I64x2Ne,
|
||||||
|
De::F32x4Ne => En::F32x4Ne,
|
||||||
|
De::F64x2Ne => En::F64x2Ne,
|
||||||
|
De::I8x16LtS => En::I8x16LtS,
|
||||||
|
De::I8x16LtU => En::I8x16LtU,
|
||||||
|
De::I16x8LtS => En::I16x8LtS,
|
||||||
|
De::I16x8LtU => En::I16x8LtU,
|
||||||
|
De::I32x4LtS => En::I32x4LtS,
|
||||||
|
De::I32x4LtU => En::I32x4LtU,
|
||||||
|
De::F32x4Lt => En::F32x4Lt,
|
||||||
|
De::F64x2Lt => En::F64x2Lt,
|
||||||
|
De::I8x16LeS => En::I8x16LeS,
|
||||||
|
De::I8x16LeU => En::I8x16LeU,
|
||||||
|
De::I16x8LeS => En::I16x8LeS,
|
||||||
|
De::I16x8LeU => En::I16x8LeU,
|
||||||
|
De::I32x4LeS => En::I32x4LeS,
|
||||||
|
De::I32x4LeU => En::I32x4LeU,
|
||||||
|
De::F32x4Le => En::F32x4Le,
|
||||||
|
De::F64x2Le => En::F64x2Le,
|
||||||
|
De::I8x16GtS => En::I8x16GtS,
|
||||||
|
De::I8x16GtU => En::I8x16GtU,
|
||||||
|
De::I16x8GtS => En::I16x8GtS,
|
||||||
|
De::I16x8GtU => En::I16x8GtU,
|
||||||
|
De::I32x4GtS => En::I32x4GtS,
|
||||||
|
De::I32x4GtU => En::I32x4GtU,
|
||||||
|
De::F32x4Gt => En::F32x4Gt,
|
||||||
|
De::F64x2Gt => En::F64x2Gt,
|
||||||
|
De::I8x16GeS => En::I8x16GeS,
|
||||||
|
De::I8x16GeU => En::I8x16GeU,
|
||||||
|
De::I16x8GeS => En::I16x8GeS,
|
||||||
|
De::I16x8GeU => En::I16x8GeU,
|
||||||
|
De::I32x4GeS => En::I32x4GeS,
|
||||||
|
De::I32x4GeU => En::I32x4GeU,
|
||||||
|
De::F32x4Ge => En::F32x4Ge,
|
||||||
|
De::F64x2Ge => En::F64x2Ge,
|
||||||
|
De::F32x4Div => En::F32x4Div,
|
||||||
|
De::F64x2Div => En::F64x2Div,
|
||||||
|
De::F32x4Sqrt => En::F32x4Sqrt,
|
||||||
|
De::F64x2Sqrt => En::F64x2Sqrt,
|
||||||
|
De::F32x4Ceil => En::F32x4Ceil,
|
||||||
|
De::F64x2Ceil => En::F64x2Ceil,
|
||||||
|
De::F32x4Floor => En::F32x4Floor,
|
||||||
|
De::F64x2Floor => En::F64x2Floor,
|
||||||
|
De::F32x4Trunc => En::F32x4Trunc,
|
||||||
|
De::F64x2Trunc => En::F64x2Trunc,
|
||||||
|
De::F32x4Nearest => En::F32x4Nearest,
|
||||||
|
De::F64x2Nearest => En::F64x2Nearest,
|
||||||
|
De::F32x4ConvertI32x4S => En::F32x4ConvertI32x4S,
|
||||||
|
De::F32x4ConvertI32x4U => En::F32x4ConvertI32x4U,
|
||||||
|
De::F64x2ConvertLowI32x4S => En::F64x2ConvertLowI32x4S,
|
||||||
|
De::F64x2ConvertLowI32x4U => En::F64x2ConvertLowI32x4U,
|
||||||
|
De::I32x4TruncSatF32x4S => En::I32x4TruncSatF32x4S,
|
||||||
|
De::I32x4TruncSatF32x4U => En::I32x4TruncSatF32x4U,
|
||||||
|
De::I32x4TruncSatF64x2SZero => En::I32x4TruncSatF64x2SZero,
|
||||||
|
De::I32x4TruncSatF64x2UZero => En::I32x4TruncSatF64x2UZero,
|
||||||
|
De::F32x4DemoteF64x2Zero => En::F32x4DemoteF64x2Zero,
|
||||||
|
De::F64x2PromoteLowF32x4 => En::F64x2PromoteLowF32x4,
|
||||||
|
De::I8x16NarrowI16x8S => En::I8x16NarrowI16x8S,
|
||||||
|
De::I8x16NarrowI16x8U => En::I8x16NarrowI16x8U,
|
||||||
|
De::I16x8NarrowI32x4S => En::I16x8NarrowI32x4S,
|
||||||
|
De::I16x8NarrowI32x4U => En::I16x8NarrowI32x4U,
|
||||||
|
De::I16x8ExtendLowI8x16S => En::I16x8ExtendLowI8x16S,
|
||||||
|
De::I16x8ExtendHighI8x16S => En::I16x8ExtendHighI8x16S,
|
||||||
|
De::I16x8ExtendLowI8x16U => En::I16x8ExtendLowI8x16U,
|
||||||
|
De::I16x8ExtendHighI8x16U => En::I16x8ExtendHighI8x16U,
|
||||||
|
De::I32x4ExtendLowI16x8S => En::I32x4ExtendLowI16x8S,
|
||||||
|
De::I32x4ExtendHighI16x8S => En::I32x4ExtendHighI16x8S,
|
||||||
|
De::I32x4ExtendLowI16x8U => En::I32x4ExtendLowI16x8U,
|
||||||
|
De::I32x4ExtendHighI16x8U => En::I32x4ExtendHighI16x8U,
|
||||||
|
De::I64x2ExtendLowI32x4S => En::I64x2ExtendLowI32x4S,
|
||||||
|
De::I64x2ExtendHighI32x4S => En::I64x2ExtendHighI32x4S,
|
||||||
|
De::I64x2ExtendLowI32x4U => En::I64x2ExtendLowI32x4U,
|
||||||
|
De::I64x2ExtendHighI32x4U => En::I64x2ExtendHighI32x4U,
|
||||||
|
|
||||||
|
De::I8x16Shuffle { lanes } => En::I8x16Shuffle(lanes),
|
||||||
|
|
||||||
other => bail!("Unsupported instruction {:?}", other),
|
other => bail!("Unsupported instruction {:?}", other),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+2068
File diff suppressed because it is too large
Load Diff
@@ -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.28.6"
|
||||||
|
env_logger = "0.10"
|
||||||
|
log = "0.4"
|
||||||
|
pico-args = "0.5"
|
||||||
|
wgpu = "0.17"
|
||||||
|
pollster = "0.3.0"
|
||||||
|
bytemuck = { version = "1.13", features = [ "derive" ] }
|
||||||
|
anyhow = "1"
|
||||||
|
minifb = { version = "0.25.0", default-features = false, features = ["x11"] }
|
||||||
|
winapi = { version = "0.3.9", features = [ "timeapi" ] }
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Uniforms {
|
||||||
|
texture_scale: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
@builtin(vertex_index) in_vertex_index: u32,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
let i = in_vertex_index / 3u + in_vertex_index % 3u;
|
||||||
|
let x = 0.0 + f32(i % 2u) * 320.0;
|
||||||
|
let y = 0.0 + f32(i / 2u) * 240.0;
|
||||||
|
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0);
|
||||||
|
out.tex_coords = vec2<f32>(x, y);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1) var linear_sampler: sampler;
|
||||||
|
|
||||||
|
fn row_factor(offset: f32) -> f32 {
|
||||||
|
return 1.0 / (1.0 + offset * offset * 16.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn col_factor(offset: f32) -> f32 {
|
||||||
|
let o = max(0.0, abs(offset) - 0.4);
|
||||||
|
return 1.0 / (1.0 + o * o * 16.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_screen(tex_coords: vec2<f32>) -> vec4<f32> {
|
||||||
|
let base = round(tex_coords) - vec2<f32>(0.5);
|
||||||
|
let frac = tex_coords - base;
|
||||||
|
|
||||||
|
let top_factor = row_factor(frac.y);
|
||||||
|
let bottom_factor = row_factor(frac.y - 1.0);
|
||||||
|
|
||||||
|
let v = base.y + bottom_factor / (bottom_factor + top_factor);
|
||||||
|
|
||||||
|
let left_factor = col_factor(frac.x);
|
||||||
|
let right_factor = col_factor(frac.x - 1.0);
|
||||||
|
|
||||||
|
let u = base.x + right_factor / (right_factor + left_factor);
|
||||||
|
|
||||||
|
return textureSample(screen_texture, linear_sampler, vec2<f32>(u, v) / vec2<f32>(320.0, 240.0)) * (top_factor + bottom_factor) * (left_factor + right_factor) * 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return sample_screen(in.tex_coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main_chromatic(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let r = sample_screen(in.tex_coords + vec2<f32>(0.2, 0.2)).r;
|
||||||
|
let g = sample_screen(in.tex_coords + vec2<f32>(0.07, -0.27)).g;
|
||||||
|
let b = sample_screen(in.tex_coords + vec2<f32>(-0.27, 0.07)).b;
|
||||||
|
return vec4<f32>(r, g, b, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,554 @@
|
|||||||
|
use crate::{Input, WindowConfig, WindowImpl};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use winit::{
|
||||||
|
dpi::PhysicalSize,
|
||||||
|
event::{Event, VirtualKeyCode, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::{Fullscreen, WindowBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
use winit::platform::run_return::EventLoopExtRunReturn;
|
||||||
|
|
||||||
|
mod crt;
|
||||||
|
mod fast_crt;
|
||||||
|
mod square;
|
||||||
|
|
||||||
|
use crt::CrtFilter;
|
||||||
|
use fast_crt::FastCrtFilter;
|
||||||
|
use square::SquareFilter;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
_instance: wgpu::Instance,
|
||||||
|
surface: wgpu::Surface,
|
||||||
|
_adapter: wgpu::Adapter,
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
palette_screen_mode: PaletteScreenMode,
|
||||||
|
surface_config: wgpu::SurfaceConfiguration,
|
||||||
|
filter: Box<dyn Filter>,
|
||||||
|
event_loop: EventLoop<()>,
|
||||||
|
window: winit::window::Window,
|
||||||
|
gamepads: [u8; 4],
|
||||||
|
next_frame: Instant,
|
||||||
|
is_fullscreen: bool,
|
||||||
|
is_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(window_config: WindowConfig) -> Result<Window> {
|
||||||
|
async fn create(window_config: WindowConfig) -> Result<Window> {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_inner_size(PhysicalSize::new(
|
||||||
|
(320. * window_config.scale).round() as u32,
|
||||||
|
(240. * window_config.scale).round() as u32,
|
||||||
|
))
|
||||||
|
.with_min_inner_size(PhysicalSize::new(320u32, 240))
|
||||||
|
.with_title("MicroW8")
|
||||||
|
.with_fullscreen(if window_config.fullscreen {
|
||||||
|
Some(Fullscreen::Borderless(None))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.build(&event_loop)?;
|
||||||
|
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
|
||||||
|
let instance = wgpu::Instance::new(Default::default());
|
||||||
|
let surface = unsafe { instance.create_surface(&window) }?;
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::LowPower,
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
force_fallback_adapter: false,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| anyhow!("Request adapter failed"))?;
|
||||||
|
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(&wgpu::DeviceDescriptor::default(), None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let palette_screen_mode = PaletteScreenMode::new(&device);
|
||||||
|
|
||||||
|
let surface_config = wgpu::SurfaceConfiguration {
|
||||||
|
present_mode: wgpu::PresentMode::AutoNoVsync,
|
||||||
|
..surface
|
||||||
|
.get_default_config(
|
||||||
|
&adapter,
|
||||||
|
window.inner_size().width,
|
||||||
|
window.inner_size().height,
|
||||||
|
)
|
||||||
|
.expect("Surface incompatible with adapter")
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter: Box<dyn Filter> = create_filter(
|
||||||
|
&device,
|
||||||
|
&palette_screen_mode.screen_view,
|
||||||
|
window.inner_size(),
|
||||||
|
surface_config.format,
|
||||||
|
window_config.filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
surface.configure(&device, &surface_config);
|
||||||
|
|
||||||
|
Ok(Window {
|
||||||
|
event_loop,
|
||||||
|
window,
|
||||||
|
_instance: instance,
|
||||||
|
surface,
|
||||||
|
_adapter: adapter,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
palette_screen_mode,
|
||||||
|
surface_config,
|
||||||
|
filter,
|
||||||
|
gamepads: [0; 4],
|
||||||
|
next_frame: Instant::now(),
|
||||||
|
is_fullscreen: window_config.fullscreen,
|
||||||
|
is_open: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pollster::block_on(create(window_config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowImpl for Window {
|
||||||
|
fn begin_frame(&mut self) -> Input {
|
||||||
|
let mut reset = false;
|
||||||
|
self.event_loop.run_return(|event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::WaitUntil(self.next_frame);
|
||||||
|
let mut new_filter = None;
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::Resized(new_size) => {
|
||||||
|
self.surface_config.width = new_size.width;
|
||||||
|
self.surface_config.height = new_size.height;
|
||||||
|
self.surface.configure(&self.device, &self.surface_config);
|
||||||
|
self.filter.resize(&self.queue, new_size);
|
||||||
|
}
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
self.is_open = false;
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { input, .. } => {
|
||||||
|
fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
|
||||||
|
match input.scancode {
|
||||||
|
44 => 16,
|
||||||
|
45 => 32,
|
||||||
|
30 => 64,
|
||||||
|
31 => 128,
|
||||||
|
_ => match input.virtual_keycode {
|
||||||
|
Some(VirtualKeyCode::Up) => 1,
|
||||||
|
Some(VirtualKeyCode::Down) => 2,
|
||||||
|
Some(VirtualKeyCode::Left) => 4,
|
||||||
|
Some(VirtualKeyCode::Right) => 8,
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input.state == winit::event::ElementState::Pressed {
|
||||||
|
match input.virtual_keycode {
|
||||||
|
Some(VirtualKeyCode::Escape) => {
|
||||||
|
self.is_open = false;
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
Some(VirtualKeyCode::F) => {
|
||||||
|
let fullscreen = if self.window.fullscreen().is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Fullscreen::Borderless(None))
|
||||||
|
};
|
||||||
|
self.is_fullscreen = fullscreen.is_some();
|
||||||
|
self.window.set_fullscreen(fullscreen);
|
||||||
|
}
|
||||||
|
Some(VirtualKeyCode::R) => reset = true,
|
||||||
|
Some(VirtualKeyCode::Key1) => new_filter = Some(1),
|
||||||
|
Some(VirtualKeyCode::Key2) => new_filter = Some(2),
|
||||||
|
Some(VirtualKeyCode::Key3) => new_filter = Some(3),
|
||||||
|
Some(VirtualKeyCode::Key4) => new_filter = Some(4),
|
||||||
|
Some(VirtualKeyCode::Key5) => new_filter = Some(5),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gamepads[0] |= gamepad_button(&input);
|
||||||
|
} else {
|
||||||
|
self.gamepads[0] &= !gamepad_button(&input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Event::RedrawEventsCleared => {
|
||||||
|
if Instant::now() >= self.next_frame
|
||||||
|
// workaround needed on Wayland until the next winit release
|
||||||
|
&& self.window.fullscreen().is_some() == self.is_fullscreen
|
||||||
|
{
|
||||||
|
*control_flow = ControlFlow::Exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
if let Some(new_filter) = new_filter {
|
||||||
|
self.filter = create_filter(
|
||||||
|
&self.device,
|
||||||
|
&self.palette_screen_mode.screen_view,
|
||||||
|
self.window.inner_size(),
|
||||||
|
self.surface_config.format,
|
||||||
|
new_filter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Input {
|
||||||
|
gamepads: self.gamepads,
|
||||||
|
reset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||||
|
self.next_frame = next_frame;
|
||||||
|
self.palette_screen_mode
|
||||||
|
.write_framebuffer(&self.queue, framebuffer);
|
||||||
|
self.palette_screen_mode.write_palette(&self.queue, palette);
|
||||||
|
|
||||||
|
let output = self.surface.get_current_texture().unwrap();
|
||||||
|
let view = output
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
self.palette_screen_mode.resolve_screen(&mut encoder);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 1.0,
|
||||||
|
}),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.filter.render(&mut render_pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_open(&self) -> bool {
|
||||||
|
self.is_open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_filter(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
screen_texture: &wgpu::TextureView,
|
||||||
|
window_size: PhysicalSize<u32>,
|
||||||
|
surface_format: wgpu::TextureFormat,
|
||||||
|
filter: u32,
|
||||||
|
) -> Box<dyn Filter> {
|
||||||
|
match filter {
|
||||||
|
1 => Box::new(SquareFilter::new(
|
||||||
|
device,
|
||||||
|
screen_texture,
|
||||||
|
window_size,
|
||||||
|
surface_format,
|
||||||
|
)),
|
||||||
|
2 => Box::new(FastCrtFilter::new(
|
||||||
|
device,
|
||||||
|
screen_texture,
|
||||||
|
window_size,
|
||||||
|
surface_format,
|
||||||
|
false,
|
||||||
|
)),
|
||||||
|
3 => Box::new(CrtFilter::new(
|
||||||
|
device,
|
||||||
|
screen_texture,
|
||||||
|
window_size,
|
||||||
|
surface_format,
|
||||||
|
)),
|
||||||
|
4 => Box::new(FastCrtFilter::new(
|
||||||
|
device,
|
||||||
|
screen_texture,
|
||||||
|
window_size,
|
||||||
|
surface_format,
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
_ => Box::new(AutoCrtFilter::new(
|
||||||
|
device,
|
||||||
|
screen_texture,
|
||||||
|
window_size,
|
||||||
|
surface_format,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Filter {
|
||||||
|
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>);
|
||||||
|
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AutoCrtFilter {
|
||||||
|
small: CrtFilter,
|
||||||
|
large: FastCrtFilter,
|
||||||
|
resolution: PhysicalSize<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoCrtFilter {
|
||||||
|
fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
screen: &wgpu::TextureView,
|
||||||
|
resolution: PhysicalSize<u32>,
|
||||||
|
surface_format: wgpu::TextureFormat,
|
||||||
|
) -> AutoCrtFilter {
|
||||||
|
let small = CrtFilter::new(device, screen, resolution, surface_format);
|
||||||
|
let large = FastCrtFilter::new(device, screen, resolution, surface_format, true);
|
||||||
|
AutoCrtFilter {
|
||||||
|
small,
|
||||||
|
large,
|
||||||
|
resolution,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Filter for AutoCrtFilter {
|
||||||
|
fn resize(&mut self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
|
||||||
|
self.small.resize(queue, new_size);
|
||||||
|
self.large.resize(queue, new_size);
|
||||||
|
self.resolution = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||||
|
if self.resolution.width < 960 || self.resolution.height < 720 {
|
||||||
|
self.small.render(render_pass);
|
||||||
|
} else {
|
||||||
|
self.large.render(render_pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PaletteScreenMode {
|
||||||
|
framebuffer: wgpu::Texture,
|
||||||
|
palette: wgpu::Texture,
|
||||||
|
screen_view: wgpu::TextureView,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaletteScreenMode {
|
||||||
|
fn new(device: &wgpu::Device) -> PaletteScreenMode {
|
||||||
|
let framebuffer_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::R8Uint,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
label: None,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: 256,
|
||||||
|
height: 1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D1,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
label: None,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
label: None,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let framebuffer_texture_view =
|
||||||
|
framebuffer_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let palette_texture_view =
|
||||||
|
palette_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let screen_texture_view =
|
||||||
|
screen_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let palette_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Uint,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D1,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let palette_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &palette_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&framebuffer_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&palette_texture_view),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let palette_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(include_str!("palette.wgsl").into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let palette_pipeline_layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[&palette_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let palette_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&palette_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &palette_shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &palette_shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: Default::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: Default::default(),
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
PaletteScreenMode {
|
||||||
|
framebuffer: framebuffer_texture,
|
||||||
|
palette: palette_texture,
|
||||||
|
screen_view: screen_texture_view,
|
||||||
|
bind_group: palette_bind_group,
|
||||||
|
pipeline: palette_pipeline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_framebuffer(&self, queue: &wgpu::Queue, pixels: &[u8]) {
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &self.framebuffer,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
&bytemuck::cast_slice(pixels),
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(320),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_palette(&self, queue: &wgpu::Queue, palette: &[u8]) {
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
texture: &self.palette,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
&bytemuck::cast_slice(palette),
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(256 * 4),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width: 256,
|
||||||
|
height: 1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_screen(&self, encoder: &mut wgpu::CommandEncoder) {
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &self.screen_view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&self.pipeline);
|
||||||
|
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
render_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
mod cpu;
|
||||||
|
mod gpu;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
inner: Box<dyn WindowImpl>,
|
||||||
|
fps_counter: Option<FpsCounter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FpsCounter {
|
||||||
|
start: Instant,
|
||||||
|
num_frames: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(mut config: WindowConfig) -> Result<Window> {
|
||||||
|
let fps_counter = if config.fps_counter {
|
||||||
|
Some(FpsCounter {
|
||||||
|
start: Instant::now(),
|
||||||
|
num_frames: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
config.scale = config.scale.max(1.).min(20.);
|
||||||
|
if config.enable_gpu {
|
||||||
|
match gpu::Window::new(config) {
|
||||||
|
Ok(window) => {
|
||||||
|
return Ok(Window {
|
||||||
|
inner: Box::new(window),
|
||||||
|
fps_counter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!(
|
||||||
|
"Failed to create gpu window: {}\nFalling back tp cpu window",
|
||||||
|
err
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cpu::Window::new().map(|window| Window {
|
||||||
|
inner: Box::new(window),
|
||||||
|
fps_counter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_frame(&mut self) -> Input {
|
||||||
|
self.inner.begin_frame()
|
||||||
|
}
|
||||||
|
pub fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||||
|
self.inner.end_frame(framebuffer, palette, next_frame);
|
||||||
|
if let Some(ref mut fps_counter) = self.fps_counter {
|
||||||
|
fps_counter.num_frames += 1;
|
||||||
|
let elapsed = fps_counter.start.elapsed().as_secs_f32();
|
||||||
|
if elapsed >= 1.0 {
|
||||||
|
println!("fps: {:.1}", fps_counter.num_frames as f32 / elapsed);
|
||||||
|
fps_counter.num_frames = 0;
|
||||||
|
fps_counter.start = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.inner.is_open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WindowConfig {
|
||||||
|
enable_gpu: bool,
|
||||||
|
filter: u32,
|
||||||
|
fullscreen: bool,
|
||||||
|
fps_counter: bool,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowConfig {
|
||||||
|
fn default() -> WindowConfig {
|
||||||
|
WindowConfig {
|
||||||
|
enable_gpu: true,
|
||||||
|
filter: 5,
|
||||||
|
fullscreen: false,
|
||||||
|
fps_counter: false,
|
||||||
|
scale: 2.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowConfig {
|
||||||
|
pub fn parse_arguments(&mut self, args: &mut pico_args::Arguments) {
|
||||||
|
self.enable_gpu = !args.contains("--no-gpu");
|
||||||
|
if let Some(filter) = args.opt_value_from_str::<_, String>("--filter").unwrap() {
|
||||||
|
self.filter = match filter.as_str() {
|
||||||
|
"1" | "nearest" => 1,
|
||||||
|
"2" | "fast_crt" => 2,
|
||||||
|
"3" | "ss_crt" => 3,
|
||||||
|
"4" | "chromatic" => 4,
|
||||||
|
"5" | "auto_crt" => 5,
|
||||||
|
o => {
|
||||||
|
println!("Unknown --filter '{}'", o);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.fullscreen = args.contains("--fullscreen");
|
||||||
|
self.fps_counter = args.contains("--fps");
|
||||||
|
self.scale = args
|
||||||
|
.opt_value_from_str("--scale")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap_or(self.scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Input {
|
||||||
|
pub gamepads: [u8; 4],
|
||||||
|
pub reset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WindowImpl {
|
||||||
|
fn begin_frame(&mut self) -> Input;
|
||||||
|
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant);
|
||||||
|
fn is_open(&self) -> bool;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
+1
-1
@@ -63,7 +63,7 @@ class APU extends AudioWorkletProcessor {
|
|||||||
|
|
||||||
this.memory = memory;
|
this.memory = memory;
|
||||||
|
|
||||||
this.snd = instance.exports.snd || platform_instance.exports.gesSnd;
|
this.snd = instance.exports.snd || platform_instance.exports.sndGes;
|
||||||
|
|
||||||
this.port.postMessage(2);
|
this.port.postMessage(2);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uw8">
|
<div id="uw8">
|
||||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0-rc3
|
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.2
|
||||||
</div>
|
</div>
|
||||||
<div id="centered">
|
<div id="centered">
|
||||||
<canvas class="screen" id="screen" width="320" height="240">
|
<canvas class="screen" id="screen" width="320" height="240">
|
||||||
|
|||||||
@@ -263,6 +263,10 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
|
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
|
||||||
updateVisibility(document.hasFocus());
|
updateVisibility(document.hasFocus());
|
||||||
|
|
||||||
|
if (instance.exports.start) {
|
||||||
|
instance.exports.start();
|
||||||
|
}
|
||||||
|
|
||||||
function mainloop() {
|
function mainloop() {
|
||||||
if (!keepRunning) {
|
if (!keepRunning) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+627
-531
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user