mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
Compare commits
24 Commits
v0.2.2
...
4e18d38538
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e18d38538 | |||
| d4bad3ba8b | |||
| 6ddda24156 | |||
| 7003db3a05 | |||
| 8258bc1854 | |||
| a651107104 | |||
| 22c35e37f4 | |||
| 805c939097 | |||
| 440e150896 | |||
| 77b2e27346 | |||
| 09e4fcbf14 | |||
| c8a9f533ef | |||
| 4a1d607bcb | |||
| bbb75838cd | |||
| dbeb242fb2 | |||
| 4fe4bce0ad | |||
| fe86153562 | |||
| c9dadaca2e | |||
| 5dc3e281ce | |||
| ce3afb821f | |||
| 2652a351ad | |||
| 9109722409 | |||
| f861c262a1 | |||
| bbfb5eba49 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -30,9 +30,9 @@ jobs:
|
||||
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache build dirs
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Build
|
||||
run: cargo build --release --verbose
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: uw8-${{ matrix.build }}
|
||||
path: target/release/${{ matrix.exe }}
|
||||
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -8,12 +8,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: build_and_deploy
|
||||
uses: shalzz/zola-deploy-action@v0.14.1
|
||||
uses: shalzz/zola-deploy-action@70a101a14bbdeed13e7a42a9ed06b35c9e9e826e
|
||||
env:
|
||||
# Target branch
|
||||
PAGES_BRANCH: gh-pages
|
||||
BUILD_DIR: site
|
||||
# Provide personal access token
|
||||
TOKEN: $GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
3209
Cargo.lock
generated
3209
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -11,21 +11,21 @@ native = ["wasmtime", "uw8-window", "cpal", "rubato" ]
|
||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||
|
||||
[dependencies]
|
||||
wasmtime = { version = "5.0.0", optional = true }
|
||||
wasmtime = { version = "19.0.1", optional = true }
|
||||
anyhow = "1"
|
||||
env_logger = "0.10"
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4"
|
||||
uw8-window = { path = "uw8-window", optional = true }
|
||||
notify = "5"
|
||||
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||
pico-args = "0.5"
|
||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0e7ea50" }
|
||||
wat = "1"
|
||||
uw8-tool = { path = "uw8-tool" }
|
||||
same-file = "1"
|
||||
warp = { version = "0.3.3", optional = true }
|
||||
tokio = { version = "1.24.0", features = ["sync", "rt"], optional = true }
|
||||
tokio-stream = { version = "0.1.11", features = ["sync"], optional = true }
|
||||
webbrowser = { version = "0.8.6", optional = true }
|
||||
warp = { version = "0.3.6", optional = true }
|
||||
tokio = { version = "1.37.0", features = ["sync", "rt"], optional = true }
|
||||
tokio-stream = { version = "0.1.15", features = ["sync"], optional = true }
|
||||
webbrowser = { version = "0.8.13", optional = true }
|
||||
ansi_term = "0.12.1"
|
||||
cpal = { version = "0.14.2", optional = true }
|
||||
rubato = { version = "0.12.0", optional = true }
|
||||
cpal = { version = "0.15.3", optional = true }
|
||||
rubato = { version = "0.15.0", optional = true }
|
||||
|
||||
21
examples/curlywas/sprites.cwa
Normal file
21
examples/curlywas/sprites.cwa
Normal file
@@ -0,0 +1,21 @@
|
||||
include "../include/microw8-api.cwa"
|
||||
|
||||
const SPRITE = 0x20000;
|
||||
|
||||
export fn upd() {
|
||||
cls(0);
|
||||
let t = time() / 2_f;
|
||||
let i: i32;
|
||||
loop spriteLoop {
|
||||
let inline x = sin(t * -1.3 + i as f32 * (3.141 / 30_f)) * 180_f + 160_f;
|
||||
let inline y = sin(t * 1.7 + i as f32 * (3.141 / 40_f)) * 140_f + 120_f;
|
||||
blitSprite(SPRITE, 16, x as i32, y as i32, (i & 3) * 0x200 + 0x100);
|
||||
branch_if (i +:= 1) < 100: spriteLoop;
|
||||
}
|
||||
}
|
||||
|
||||
start fn start() {
|
||||
printChar('OO');
|
||||
circle(8_f, 8_f, 6_f, 75);
|
||||
grabSprite(SPRITE, 16, 0, 0, 0);
|
||||
}
|
||||
@@ -30,11 +30,13 @@ import "env.printInt" fn printInt(i32);
|
||||
import "env.setTextColor" fn setTextColor(i32);
|
||||
import "env.setBackgroundColor" fn setBackgroundColor(i32);
|
||||
import "env.setCursorPosition" fn setCursorPosition(i32, i32);
|
||||
import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
|
||||
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32);
|
||||
import "env.rectangleOutline" fn rectangleOutline(f32, f32, f32, f32, i32);
|
||||
import "env.circleOutline" fn circleOutline(f32, f32, f32, i32);
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.playNote" fn playNote(i32, i32);
|
||||
import "env.sndGes" fn sndGes(i32) -> f32;
|
||||
import "env.blitSprite" fn blitSprite(i32, i32, i32, i32, i32);
|
||||
import "env.grabSprite" fn grabSprite(i32, i32, i32, i32, i32);
|
||||
|
||||
const TIME_MS = 0x40;
|
||||
const GAMEPAD = 0x44;
|
||||
|
||||
@@ -30,11 +30,13 @@
|
||||
(import "env" "setTextColor" (func $setTextColor (param i32)))
|
||||
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
|
||||
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
|
||||
(import "env" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "rectangleOutline" (func $rectangleOutline (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "circleOutline" (func $circleOutline (param f32) (param f32) (param f32) (param i32)))
|
||||
(import "env" "exp" (func $exp (param f32) (result f32)))
|
||||
(import "env" "playNote" (func $playNote (param i32) (param i32)))
|
||||
(import "env" "sndGes" (func $sndGes (param i32) (result f32)))
|
||||
(import "env" "blitSprite" (func $blitSprite (param i32) (param i32) (param i32) (param i32) (param i32)))
|
||||
(import "env" "grabSprite" (func $grabSprite (param i32) (param i32) (param i32) (param i32) (param i32)))
|
||||
|
||||
;; to use defines, include this file with a preprocessor
|
||||
;; like gpp (https://logological.org/gpp).
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -62,7 +62,6 @@ export fn sndGes(t: i32) -> f32 {
|
||||
let phase = channelState!GesChannelState.Phase;
|
||||
|
||||
let inline pulseWidth = channelReg?1;
|
||||
let phaseShift = (pulseWidth - 128) * 255;
|
||||
let invPhaseInc = 1 as f32 / phaseInc as f32;
|
||||
|
||||
i = 0;
|
||||
@@ -131,7 +130,7 @@ export fn sndGes(t: i32) -> f32 {
|
||||
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||
|
||||
let phase = channelState!GesChannelState.Phase;
|
||||
if modSrc > ch {
|
||||
if modSrc < ch {
|
||||
phase = phase - (phaseInc << 6);
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export fn rectangle(x: f32, y: f32, w: f32, h: f32, col: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
export fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, col: i32) {
|
||||
export fn rectangleOutline(x: f32, y: f32, w: f32, h: f32, col: i32) {
|
||||
let xl = nearest(x) as i32;
|
||||
let xr = nearest(x + w) as i32;
|
||||
let yt = nearest(y) as i32;
|
||||
@@ -212,7 +212,7 @@ export fn circle(cx: f32, cy: f32, radius: f32, col: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
export fn circle_outline(cx: f32, cy: f32, radius: f32, col: i32) {
|
||||
export fn circleOutline(cx: f32, cy: f32, radius: f32, col: i32) {
|
||||
let prev_w: f32;
|
||||
let y = clamp(nearest(cy - radius) as i32, -1, 241);
|
||||
let maxY = clamp(nearest(cy + radius) as i32, -1, 241);
|
||||
@@ -352,6 +352,92 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
||||
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
|
||||
}
|
||||
|
||||
export fn blitSprite(sprite: i32, size: i32, x: i32, y: i32, control: i32) {
|
||||
let lazy width = size & 65535;
|
||||
let lazy height = select(size >> 16, size >> 16, width);
|
||||
|
||||
let lazy x0 = select(x < 0, -x, 0);
|
||||
let lazy x1 = select(x + width > 320, 320 - x, width);
|
||||
let lazy y0 = select(y < 0, -y, 0);
|
||||
let lazy y1 = select(y + height > 240, 240 - y, height);
|
||||
|
||||
let lazy numRows = y1 - y0;
|
||||
let lazy numCols = x1 - x0;
|
||||
if numRows <= 0 | numCols <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let trans = (control & 511) - 256;
|
||||
let lazy flip_x = 1 - ((control >> 8) & 2);
|
||||
let lazy flip_y = 1 - ((control >> 9) & 2);
|
||||
if flip_x < 0 {
|
||||
sprite += width - 1;
|
||||
}
|
||||
if flip_y < 0 {
|
||||
sprite += (height - 1) * width;
|
||||
}
|
||||
|
||||
let spriteRow = sprite + x0 * flip_x + y0 * flip_y * width;
|
||||
let screenRow = x + x0 + (y + y0) * 320;
|
||||
|
||||
loop yloop {
|
||||
let lx = 0;
|
||||
loop xloop {
|
||||
let lazy col = (spriteRow + lx * flip_x)?0;
|
||||
if col != trans {
|
||||
(screenRow + lx)?120 = col;
|
||||
}
|
||||
branch_if (lx +:= 1) < numCols: xloop;
|
||||
}
|
||||
spriteRow += width * flip_y;
|
||||
screenRow += 320;
|
||||
branch_if numRows -:= 1: yloop;
|
||||
}
|
||||
}
|
||||
|
||||
export fn grabSprite(sprite: i32, size: i32, x: i32, y: i32, control: i32) {
|
||||
let lazy width = size & 65535;
|
||||
let lazy height = select(size >> 16, size >> 16, width);
|
||||
|
||||
let lazy x0 = select(x < 0, -x, 0);
|
||||
let lazy x1 = select(x + width > 320, 320 - x, width);
|
||||
let lazy y0 = select(y < 0, -y, 0);
|
||||
let lazy y1 = select(y + height > 240, 240 - y, height);
|
||||
|
||||
let lazy numRows = y1 - y0;
|
||||
let lazy numCols = x1 - x0;
|
||||
if numRows <= 0 | numCols <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let trans = (control & 511) - 256;
|
||||
let lazy flip_x = 1 - ((control >> 8) & 2);
|
||||
let lazy flip_y = 1 - ((control >> 9) & 2);
|
||||
if flip_x < 0 {
|
||||
sprite += width - 1;
|
||||
}
|
||||
if flip_y < 0 {
|
||||
sprite += (height - 1) * width;
|
||||
}
|
||||
|
||||
let spriteRow = sprite + x0 * flip_x + y0 * flip_y * width;
|
||||
let screenRow = x + x0 + (y + y0) * 320;
|
||||
|
||||
loop yloop {
|
||||
let lx = 0;
|
||||
loop xloop {
|
||||
let lazy col = (screenRow + lx)?120;
|
||||
if col != trans {
|
||||
(spriteRow + lx * flip_x)?0 = col;
|
||||
}
|
||||
branch_if (lx +:= 1) < numCols: xloop;
|
||||
}
|
||||
spriteRow += width * flip_y;
|
||||
screenRow += 320;
|
||||
branch_if numRows -:= 1: yloop;
|
||||
}
|
||||
}
|
||||
|
||||
//////////
|
||||
// TEXT //
|
||||
//////////
|
||||
@@ -361,6 +447,7 @@ global mut textCursorY = 0;
|
||||
global mut textColor = 15;
|
||||
global mut bgColor = 0;
|
||||
global mut outputChannel = 0;
|
||||
global mut textScale = 1;
|
||||
|
||||
export fn printChar(char: i32) {
|
||||
loop chars {
|
||||
@@ -372,6 +459,8 @@ export fn printChar(char: i32) {
|
||||
global mut controlCodeLength = 0;
|
||||
|
||||
fn printSingleChar(char: i32) {
|
||||
let charSize = 8 * textScale;
|
||||
|
||||
if outputChannel >= 2 & (char < 4 | char > 6) {
|
||||
logChar(char);
|
||||
return;
|
||||
@@ -405,9 +494,9 @@ fn printSingleChar(char: i32) {
|
||||
}
|
||||
|
||||
if char == 8 {
|
||||
textCursorX = textCursorX - 8;
|
||||
textCursorX = textCursorX - charSize;
|
||||
if !outputChannel & textCursorX < 0 {
|
||||
textCursorX = 320-8;
|
||||
textCursorX = 320-charSize;
|
||||
printSingleChar(11);
|
||||
}
|
||||
return;
|
||||
@@ -417,34 +506,34 @@ fn printSingleChar(char: i32) {
|
||||
if !outputChannel & textCursorX >= 320 {
|
||||
printChar(0xd0a);
|
||||
}
|
||||
textCursorX = textCursorX + 8;
|
||||
textCursorX = textCursorX + charSize;
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 10 {
|
||||
textCursorY = textCursorY + 8;
|
||||
textCursorY = textCursorY + charSize;
|
||||
if !outputChannel & textCursorY >= 240 {
|
||||
textCursorY = 240 - 8;
|
||||
textCursorY = 240 - charSize;
|
||||
let i: i32;
|
||||
loop scroll_copy {
|
||||
i!120 = i!(120 + 320 * 8);
|
||||
branch_if (i := i + 4) < 320 * (240 - 8): scroll_copy;
|
||||
i!120 = (i + 320 * charSize)!120;
|
||||
branch_if (i := i + 4) < 320 * (240 - charSize): scroll_copy;
|
||||
}
|
||||
rectangle(0 as f32, (240 - 8) as f32, 320 as f32, 8 as f32, bgColor);
|
||||
rectangle(0 as f32, (240 - charSize) as f32, 320 as f32, charSize as f32, bgColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 11 {
|
||||
textCursorY = textCursorY - 8;
|
||||
textCursorY = textCursorY - charSize;
|
||||
if !outputChannel & textCursorY < 0 {
|
||||
textCursorY = 0;
|
||||
let i = 320 * (240 - 8);
|
||||
let i = 320 * (240 - charSize);
|
||||
loop scroll_copy {
|
||||
i!(116 + 320 * 8) = i!116;
|
||||
(i + 320 * charSize)!116 = i!116;
|
||||
branch_if (i := i - 4): scroll_copy;
|
||||
}
|
||||
rectangle(0 as f32, 0 as f32, 320 as f32, 8 as f32, bgColor);
|
||||
rectangle(0 as f32, 0 as f32, 320 as f32, charSize as f32, bgColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -476,6 +565,12 @@ fn printSingleChar(char: i32) {
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 30 {
|
||||
let scale = 0x12d20?1;
|
||||
textScale = select(scale > 0 & scale <= 16, scale, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if char == 31 {
|
||||
textCursorX = 0x12d20?1 * (8 - outputChannel * 6);
|
||||
textCursorY = 0x12d20?2 * (8 - outputChannel * 7);
|
||||
@@ -498,7 +593,7 @@ data(0x12d00) {
|
||||
1, 1, 1, 1, // 16-19,
|
||||
1, 1, 1, 1, // 20-23,
|
||||
1, 1, 1, 1, // 24-27,
|
||||
1, 1, 1, 3 // 28-31
|
||||
1, 1, 2, 3 // 28-31
|
||||
)
|
||||
}
|
||||
|
||||
@@ -507,26 +602,28 @@ fn drawChar(char: i32) {
|
||||
printChar(0xd0a);
|
||||
}
|
||||
|
||||
let charSize = 8 * textScale;
|
||||
|
||||
let y: i32;
|
||||
loop rows {
|
||||
let bits = (char * 8 + y)?0x13400;
|
||||
let bits = (char * 8 + y / textScale)?0x13400;
|
||||
let x = 0;
|
||||
if outputChannel {
|
||||
loop pixels {
|
||||
if (bits := bits << 1) & 256 {
|
||||
if (bits << (x / textScale)) & 128 {
|
||||
setPixel(textCursorX + x, textCursorY + y, textColor);
|
||||
}
|
||||
branch_if (x := x + 1) < 8: pixels;
|
||||
branch_if (x := x + 1) < charSize: pixels;
|
||||
}
|
||||
} else {
|
||||
loop pixels {
|
||||
setPixel(textCursorX + x, textCursorY + y, select((bits := bits << 1) & 256, textColor, bgColor));
|
||||
branch_if (x := x + 1) < 8: pixels;
|
||||
setPixel(textCursorX + x, textCursorY + y, select((bits << (x / textScale)) & 128, textColor, bgColor));
|
||||
branch_if (x := x + 1) < charSize: pixels;
|
||||
}
|
||||
}
|
||||
branch_if (y := y + 1) < 8: rows;
|
||||
branch_if (y := y + 1) < charSize: rows;
|
||||
}
|
||||
textCursorX = textCursorX + 8;
|
||||
textCursorX = textCursorX + charSize;
|
||||
}
|
||||
|
||||
export fn printString(ptr: i32) {
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Docs"
|
||||
|
||||
# Overview
|
||||
|
||||
MicroW8 loads WebAssembly modules with a maximum size of 256kb. You module needs to export
|
||||
MicroW8 loads WebAssembly modules with a maximum size of 256kb. Your module needs to export
|
||||
a function `fn upd()` which will be called once per frame.
|
||||
After calling `upd` MicroW8 will display the 320x240 8bpp framebuffer located
|
||||
at offset 120 in memory with the 32bpp palette located at 0x13000.
|
||||
@@ -146,13 +146,13 @@ Fills the circle at `cx, cy` and with `radius` with the given color index.
|
||||
|
||||
(Sets all pixels where the pixel center lies inside the circle.)
|
||||
|
||||
### fn rectangle_outline(x: f32, y: f32, w: f32, h: f32, color: i32)
|
||||
### fn rectangleOutline(x: f32, y: f32, w: f32, h: f32, color: i32)
|
||||
|
||||
Draws a one pixel outline on the inside of the given rectangle.
|
||||
|
||||
(Draws the outermost pixels that are still inside the rectangle area.)
|
||||
|
||||
### fn circle_outline(cx: f32, cy: f32, radius: f32, color: i32)
|
||||
### fn circleOutline(cx: f32, cy: f32, radius: f32, color: i32)
|
||||
|
||||
Draws a one pixel outline on the inside of the given circle.
|
||||
|
||||
@@ -162,6 +162,21 @@ Draws a one pixel outline on the inside of the given circle.
|
||||
|
||||
Draws a line from `x1,y1` to `x2,y2` in the given color index.
|
||||
|
||||
### fn blitSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)
|
||||
|
||||
Copies the pixel data at `spriteData` onto the screen at `x`, `y`. The size of the sprite is passed as `width | (height << 16)`.
|
||||
If the height is given as 0, the sprite is is treated as square (width x width).
|
||||
|
||||
The control parameter controls masking and flipping of the sprite:
|
||||
* bits 0-7: color mask index
|
||||
* bit 8: switch on masked blit (pixel with color mask index are treated as transparent)
|
||||
* bit 9: flip sprite x
|
||||
* bit 10: flip sprite y
|
||||
|
||||
### fn grabSprite(spriteData: i32, size: i32, x: i32, y: i32, control: i32)
|
||||
|
||||
Copies the pixel data on the screen at `x`, `y` to `spriteData`. Parameters are exactly the same as `blitSprite`.
|
||||
|
||||
## Input
|
||||
|
||||
MicroW8 provides input from a gamepad with one D-Pad and 4 buttons, or a keyboard emulation thereof.
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
|
||||
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc};
|
||||
use notify_debouncer_mini::{
|
||||
new_debouncer,
|
||||
notify::{self, RecommendedWatcher},
|
||||
DebouncedEvent, DebouncedEventKind, Debouncer,
|
||||
};
|
||||
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
||||
|
||||
pub struct FileWatcher {
|
||||
watcher: RecommendedWatcher,
|
||||
debouncer: Debouncer<RecommendedWatcher>,
|
||||
watched_files: BTreeSet<PathBuf>,
|
||||
directories: BTreeSet<PathBuf>,
|
||||
rx: mpsc::Receiver<Event>,
|
||||
rx: mpsc::Receiver<DebouncedEvent>,
|
||||
}
|
||||
|
||||
impl FileWatcher {
|
||||
pub fn new() -> Result<FileWatcher> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let watcher = notify::recommended_watcher(move |res| match res {
|
||||
Ok(event) => _ = tx.send(event),
|
||||
Err(err) => eprintln!("Error watching for file changes: {err}"),
|
||||
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 {
|
||||
watcher,
|
||||
debouncer,
|
||||
watched_files: BTreeSet::new(),
|
||||
directories: BTreeSet::new(),
|
||||
rx,
|
||||
@@ -29,7 +39,8 @@ impl FileWatcher {
|
||||
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
|
||||
|
||||
if !self.directories.contains(parent) {
|
||||
self.watcher
|
||||
self.debouncer
|
||||
.watcher()
|
||||
.watch(parent, notify::RecursiveMode::NonRecursive)?;
|
||||
self.directories.insert(parent.to_path_buf());
|
||||
}
|
||||
@@ -41,13 +52,11 @@ impl FileWatcher {
|
||||
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
||||
match self.rx.try_recv() {
|
||||
Ok(event) => match event.kind {
|
||||
EventKind::Create(_) | EventKind::Modify(_) => {
|
||||
for path in event.paths {
|
||||
let handle = same_file::Handle::from_path(&path)?;
|
||||
for file in &self.watched_files {
|
||||
if handle == same_file::Handle::from_path(file)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
DebouncedEventKind::Any => {
|
||||
let handle = same_file::Handle::from_path(&event.path)?;
|
||||
for file in &self.watched_files {
|
||||
if handle == same_file::Handle::from_path(file)? {
|
||||
return Ok(Some(event.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use cpal::traits::*;
|
||||
use rubato::Resampler;
|
||||
use uw8_window::{Window, WindowConfig};
|
||||
use wasmtime::{
|
||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
Engine, Func, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
};
|
||||
|
||||
pub struct MicroW8 {
|
||||
@@ -90,7 +90,7 @@ impl super::Runtime for MicroW8 {
|
||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||
|
||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
||||
linker.define("env", "memory", memory)?;
|
||||
linker.define(&store, "env", "memory", memory)?;
|
||||
|
||||
let loader_instance = linker.instantiate(&mut store, &self.loader_module)?;
|
||||
let load_uw8 = loader_instance.get_typed_func::<i32, i32>(&mut store, "load_uw8")?;
|
||||
@@ -255,15 +255,12 @@ fn add_native_functions(
|
||||
}
|
||||
})?;
|
||||
for i in 0..16 {
|
||||
linker.define(
|
||||
"env",
|
||||
&format!("g_reserved{}", i),
|
||||
wasmtime::Global::new(
|
||||
&mut *store,
|
||||
GlobalType::new(ValType::I32, Mutability::Const),
|
||||
0.into(),
|
||||
)?,
|
||||
let global = wasmtime::Global::new(
|
||||
&mut *store,
|
||||
GlobalType::new(ValType::I32, Mutability::Const),
|
||||
0.into(),
|
||||
)?;
|
||||
linker.define(&store, "env", &format!("g_reserved{}", i), global)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -276,14 +273,18 @@ fn instantiate_platform(
|
||||
) -> Result<wasmtime::Instance> {
|
||||
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
||||
|
||||
for export in platform_instance.exports(&mut *store) {
|
||||
linker.define(
|
||||
"env",
|
||||
export.name(),
|
||||
export
|
||||
.into_func()
|
||||
.expect("platform surely only exports functions"),
|
||||
)?;
|
||||
let exports: Vec<(String, Func)> = platform_instance
|
||||
.exports(&mut *store)
|
||||
.map(|e| {
|
||||
(
|
||||
e.name().to_owned(),
|
||||
e.into_func()
|
||||
.expect("platform surely only exports functions"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for (name, func) in exports {
|
||||
linker.define(&store, "env", &name, func)?;
|
||||
}
|
||||
|
||||
Ok(platform_instance)
|
||||
@@ -310,7 +311,7 @@ fn init_sound(
|
||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||
|
||||
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)?;
|
||||
|
||||
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
||||
@@ -327,23 +328,26 @@ fn init_sound(
|
||||
let mut configs: Vec<_> = device
|
||||
.supported_output_configs()?
|
||||
.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();
|
||||
configs.sort_by_key(|config| {
|
||||
let rate = 44100
|
||||
.max(config.min_sample_rate().0)
|
||||
.min(config.max_sample_rate().0);
|
||||
if rate >= 44100 {
|
||||
let prio = if rate >= 44100 {
|
||||
rate - 44100
|
||||
} else {
|
||||
(44100 - rate) * 1000
|
||||
}
|
||||
};
|
||||
prio + (config.sample_format() == cpal::SampleFormat::I16) as u32
|
||||
});
|
||||
let config = configs
|
||||
.into_iter()
|
||||
.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)
|
||||
.max(config.min_sample_rate())
|
||||
.min(config.max_sample_rate());
|
||||
@@ -354,6 +358,7 @@ fn init_sound(
|
||||
cpal::BufferSize::Fixed(256.max(min).min(max))
|
||||
}
|
||||
};
|
||||
let sample_format = config.sample_format();
|
||||
let config = cpal::StreamConfig {
|
||||
buffer_size,
|
||||
..config.config()
|
||||
@@ -373,8 +378,8 @@ fn init_sound(
|
||||
None
|
||||
} else {
|
||||
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
||||
let input_buffers = rs.input_buffer_allocate();
|
||||
let output_buffers = rs.output_buffer_allocate();
|
||||
let input_buffers = rs.input_buffer_allocate(true);
|
||||
let output_buffers = rs.output_buffer_allocate(true);
|
||||
Some(Resampler {
|
||||
resampler: rs,
|
||||
input_buffers,
|
||||
@@ -386,96 +391,130 @@ fn init_sound(
|
||||
let mut sample_index = 0;
|
||||
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
|
||||
let mut current_time = 0;
|
||||
let stream = device.build_output_stream(
|
||||
&config,
|
||||
move |mut outer_buffer: &mut [f32], _| {
|
||||
let mut first_update = true;
|
||||
while let Ok(update) = rx.try_recv() {
|
||||
if first_update {
|
||||
current_time += update.time.wrapping_sub(current_time) / 8;
|
||||
first_update = false;
|
||||
}
|
||||
pending_updates.push(update);
|
||||
|
||||
let mut callback = move |mut outer_buffer: &mut [f32], _: &_| {
|
||||
let mut first_update = true;
|
||||
while let Ok(update) = rx.try_recv() {
|
||||
if first_update {
|
||||
current_time += update.time.wrapping_sub(current_time) / 8;
|
||||
first_update = false;
|
||||
}
|
||||
pending_updates.push(update);
|
||||
}
|
||||
|
||||
while !outer_buffer.is_empty() {
|
||||
store.set_epoch_deadline(30);
|
||||
while pending_updates
|
||||
.first()
|
||||
.into_iter()
|
||||
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
||||
{
|
||||
let update = pending_updates.remove(0);
|
||||
memory.write(&mut store, 80, &update.data).unwrap();
|
||||
}
|
||||
|
||||
while !outer_buffer.is_empty() {
|
||||
store.set_epoch_deadline(30);
|
||||
while pending_updates
|
||||
.first()
|
||||
.into_iter()
|
||||
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
||||
{
|
||||
let update = pending_updates.remove(0);
|
||||
memory.write(&mut store, 80, &update.data).unwrap();
|
||||
}
|
||||
let duration = if let Some(update) = pending_updates.first() {
|
||||
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
||||
} else {
|
||||
outer_buffer.len()
|
||||
};
|
||||
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
||||
|
||||
let duration = if let Some(update) = pending_updates.first() {
|
||||
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
||||
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());
|
||||
}
|
||||
|
||||
fn clamp_sample(s: f32) -> f32 {
|
||||
if s.is_nan() {
|
||||
0.0
|
||||
} else {
|
||||
outer_buffer.len()
|
||||
};
|
||||
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
||||
|
||||
let mut buffer = &mut outer_buffer[..step_size];
|
||||
|
||||
{
|
||||
let mem = memory.data_mut(&mut store);
|
||||
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
||||
s.max(-1.0).min(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut resampler) = resampler {
|
||||
while !buffer.is_empty() {
|
||||
let copy_size = resampler.output_buffers[0]
|
||||
.len()
|
||||
.saturating_sub(resampler.output_index)
|
||||
.min(buffer.len() / 2);
|
||||
if copy_size == 0 {
|
||||
resampler.input_buffers[0].clear();
|
||||
resampler.input_buffers[1].clear();
|
||||
for _ in 0..resampler.resampler.input_frames_next() {
|
||||
resampler.input_buffers[0]
|
||||
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||
resampler.input_buffers[1]
|
||||
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
|
||||
sample_index = sample_index.wrapping_add(2);
|
||||
}
|
||||
|
||||
resampler
|
||||
.resampler
|
||||
.process_into_buffer(
|
||||
&resampler.input_buffers,
|
||||
&mut resampler.output_buffers,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
resampler.output_index = 0;
|
||||
} else {
|
||||
for i in 0..copy_size {
|
||||
buffer[i * 2] =
|
||||
resampler.output_buffers[0][resampler.output_index + i];
|
||||
buffer[i * 2 + 1] =
|
||||
resampler.output_buffers[1][resampler.output_index + i];
|
||||
}
|
||||
resampler.output_index += copy_size;
|
||||
buffer = &mut buffer[copy_size * 2..];
|
||||
if let Some(ref mut resampler) = resampler {
|
||||
while !buffer.is_empty() {
|
||||
let copy_size = resampler.output_buffers[0]
|
||||
.len()
|
||||
.saturating_sub(resampler.output_index)
|
||||
.min(buffer.len() / 2);
|
||||
if copy_size == 0 {
|
||||
resampler.input_buffers[0].clear();
|
||||
resampler.input_buffers[1].clear();
|
||||
for _ in 0..resampler.resampler.input_frames_next() {
|
||||
resampler.input_buffers[0].push(clamp_sample(
|
||||
snd.call(&mut store, (sample_index,)).unwrap_or(0.0),
|
||||
));
|
||||
resampler.input_buffers[1].push(clamp_sample(
|
||||
snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0),
|
||||
));
|
||||
sample_index = sample_index.wrapping_add(2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for v in buffer {
|
||||
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
||||
sample_index = sample_index.wrapping_add(1);
|
||||
|
||||
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..];
|
||||
}
|
||||
}
|
||||
|
||||
outer_buffer = &mut outer_buffer[step_size..];
|
||||
current_time =
|
||||
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
||||
} else {
|
||||
for v in buffer {
|
||||
*v = clamp_sample(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||
sample_index = sample_index.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
dbg!(err);
|
||||
},
|
||||
)?;
|
||||
|
||||
outer_buffer = &mut outer_buffer[step_size..];
|
||||
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 })
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ import "env.memory" memory(4);
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cls" fn cls(i32);
|
||||
import "env.exp" fn exp(f32) -> f32;
|
||||
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||
|
||||
include "../platform/src/ges.cwa"
|
||||
|
||||
export fn snd(t: i32) -> f32 {
|
||||
gesSnd(t)
|
||||
sndGes(t)
|
||||
}
|
||||
|
||||
export fn upd() {
|
||||
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;
|
||||
if pulse >= 256 {
|
||||
pulse = 511 - pulse;
|
||||
|
||||
@@ -152,14 +152,14 @@ impl BaseModule {
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"rectangle_outline",
|
||||
"rectangleOutline",
|
||||
&[F32, F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"circle_outline",
|
||||
"circleOutline",
|
||||
&[F32, F32, F32, I32],
|
||||
None,
|
||||
);
|
||||
@@ -169,6 +169,21 @@ impl BaseModule {
|
||||
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
||||
add_function(&mut functions, &type_map, "sndGes", &[I32], Some(F32));
|
||||
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"blitSprite",
|
||||
&[I32, I32, I32, I32, I32],
|
||||
None,
|
||||
);
|
||||
add_function(
|
||||
&mut functions,
|
||||
&type_map,
|
||||
"grabSprite",
|
||||
&[I32, I32, I32, I32, I32],
|
||||
None,
|
||||
);
|
||||
|
||||
for i in functions.len()..64 {
|
||||
add_function(
|
||||
&mut functions,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
||||
let mut module = walrus::Module::from_file(in_path)?;
|
||||
|
||||
let exports_to_delete: Vec<_> = module.exports.iter().filter_map(|export| match export.name.as_str() {
|
||||
"upd" => None,
|
||||
_ => Some(export.id())
|
||||
}).collect();
|
||||
let exports_to_delete: Vec<_> = module
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|export| match export.name.as_str() {
|
||||
"start" | "upd" | "snd" => None,
|
||||
_ => Some(export.id()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
for id in exports_to_delete {
|
||||
module.exports.delete(id);
|
||||
@@ -18,4 +22,4 @@ pub fn filter_exports(in_path: &Path, out_path: &Path) -> Result<()> {
|
||||
module.emit_wasm_file(out_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,7 +767,10 @@ fn remap_function(
|
||||
De::End => En::End,
|
||||
De::Br { relative_depth } => En::Br(relative_depth),
|
||||
De::BrIf { relative_depth } => En::BrIf(relative_depth),
|
||||
De::BrTable { .. } => todo!(),
|
||||
De::BrTable { targets } => En::BrTable(
|
||||
targets.targets().collect::<Result<Vec<u32>, _>>()?.into(),
|
||||
targets.default(),
|
||||
),
|
||||
De::Return => En::Return,
|
||||
De::Call { function_index } => En::Call(
|
||||
*function_map
|
||||
|
||||
1723
uw8-window/Cargo.lock
generated
1723
uw8-window/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,13 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
winit = "0.27.5"
|
||||
env_logger = "0.10"
|
||||
winit = "0.29.15"
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4"
|
||||
pico-args = "0.5"
|
||||
wgpu = "0.15"
|
||||
pollster = "0.2.5"
|
||||
bytemuck = { version = "1.13", features = [ "derive" ] }
|
||||
wgpu = "0.19.3"
|
||||
pollster = "0.3.0"
|
||||
bytemuck = { version = "1.15", features = [ "derive" ] }
|
||||
anyhow = "1"
|
||||
minifb = { version = "0.23.0", default-features = false, features = ["x11"] }
|
||||
minifb = { version = "0.25.0", default-features = false, features = ["x11"] }
|
||||
winapi = { version = "0.3.9", features = [ "timeapi" ] }
|
||||
|
||||
@@ -30,8 +30,8 @@ fn row_factor(offset: f32) -> f32 {
|
||||
}
|
||||
|
||||
fn col_factor(offset: f32) -> f32 {
|
||||
let offset = max(0.0, abs(offset) - 0.4);
|
||||
return 1.0 / (1.0 + offset * offset * 16.0);
|
||||
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> {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::{Input, WindowConfig, WindowImpl};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{num::NonZeroU32, time::Instant};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, VirtualKeyCode, WindowEvent},
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::{Key, KeyCode, NamedKey, PhysicalKey},
|
||||
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
use winit::platform::run_return::EventLoopExtRunReturn;
|
||||
|
||||
mod crt;
|
||||
mod fast_crt;
|
||||
mod square;
|
||||
@@ -21,7 +21,7 @@ use square::SquareFilter;
|
||||
|
||||
pub struct Window {
|
||||
_instance: wgpu::Instance,
|
||||
surface: wgpu::Surface,
|
||||
surface: wgpu::Surface<'static>,
|
||||
_adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
@@ -29,7 +29,7 @@ pub struct Window {
|
||||
surface_config: wgpu::SurfaceConfiguration,
|
||||
filter: Box<dyn Filter>,
|
||||
event_loop: EventLoop<()>,
|
||||
window: winit::window::Window,
|
||||
window: Arc<winit::window::Window>,
|
||||
gamepads: [u8; 4],
|
||||
next_frame: Instant,
|
||||
is_fullscreen: bool,
|
||||
@@ -39,9 +39,12 @@ pub struct Window {
|
||||
impl Window {
|
||||
pub fn new(window_config: WindowConfig) -> Result<Window> {
|
||||
async fn create(window_config: WindowConfig) -> Result<Window> {
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new()?;
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(640u32, 480))
|
||||
.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 {
|
||||
@@ -53,8 +56,10 @@ impl Window {
|
||||
|
||||
window.set_cursor_visible(false);
|
||||
|
||||
let window = Arc::new(window);
|
||||
|
||||
let instance = wgpu::Instance::new(Default::default());
|
||||
let surface = unsafe { instance.create_surface(&window) }?;
|
||||
let surface = instance.create_surface(window.clone())?;
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
@@ -72,7 +77,13 @@ impl Window {
|
||||
|
||||
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")
|
||||
..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(
|
||||
@@ -110,88 +121,93 @@ impl Window {
|
||||
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,
|
||||
},
|
||||
}
|
||||
self.event_loop
|
||||
.set_control_flow(ControlFlow::WaitUntil(self.next_frame));
|
||||
while self.is_open {
|
||||
let timeout = self.next_frame.saturating_duration_since(Instant::now());
|
||||
let status = self.event_loop.pump_events(Some(timeout), |event, elwt| {
|
||||
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);
|
||||
}
|
||||
if input.state == winit::event::ElementState::Pressed {
|
||||
match input.virtual_keycode {
|
||||
Some(VirtualKeyCode::Escape) => {
|
||||
self.is_open = false;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
WindowEvent::CloseRequested => {
|
||||
elwt.exit();
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
fn gamepad_button(input: &winit::event::KeyEvent) -> u8 {
|
||||
match input.physical_key {
|
||||
PhysicalKey::Code(KeyCode::KeyZ) => 16,
|
||||
PhysicalKey::Code(KeyCode::KeyX) => 32,
|
||||
PhysicalKey::Code(KeyCode::KeyA) => 64,
|
||||
PhysicalKey::Code(KeyCode::KeyS) => 128,
|
||||
_ => match input.logical_key {
|
||||
Key::Named(NamedKey::ArrowUp) => 1,
|
||||
Key::Named(NamedKey::ArrowDown) => 2,
|
||||
Key::Named(NamedKey::ArrowLeft) => 4,
|
||||
Key::Named(NamedKey::ArrowRight) => 8,
|
||||
_ => 0,
|
||||
},
|
||||
}
|
||||
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),
|
||||
_ => (),
|
||||
}
|
||||
if event.state == winit::event::ElementState::Pressed {
|
||||
match event.logical_key {
|
||||
Key::Named(NamedKey::Escape) => {
|
||||
elwt.exit();
|
||||
}
|
||||
Key::Character(ref c) => match c.as_str() {
|
||||
"f" => {
|
||||
let fullscreen = if self.window.fullscreen().is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(Fullscreen::Borderless(None))
|
||||
};
|
||||
self.is_fullscreen = fullscreen.is_some();
|
||||
self.window.set_fullscreen(fullscreen);
|
||||
}
|
||||
"r" => reset = true,
|
||||
"1" => new_filter = Some(1),
|
||||
"2" => new_filter = Some(2),
|
||||
"3" => new_filter = Some(3),
|
||||
"4" => new_filter = Some(4),
|
||||
"5" => new_filter = Some(5),
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.gamepads[0] |= gamepad_button(&input);
|
||||
} else {
|
||||
self.gamepads[0] &= !gamepad_button(&input);
|
||||
self.gamepads[0] |= gamepad_button(&event);
|
||||
} else {
|
||||
self.gamepads[0] &= !gamepad_button(&event);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawEventsCleared => {
|
||||
if Instant::now() >= self.next_frame
|
||||
// workaround needed on Wayland until the next winit release
|
||||
&& self.window.fullscreen().is_some() == self.is_fullscreen
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
if let Some(new_filter) = new_filter {
|
||||
self.filter = create_filter(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
new_filter,
|
||||
);
|
||||
}
|
||||
});
|
||||
match status {
|
||||
PumpStatus::Exit(_) => self.is_open = false,
|
||||
_ => (),
|
||||
}
|
||||
if let Some(new_filter) = new_filter {
|
||||
self.filter = create_filter(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
new_filter,
|
||||
);
|
||||
|
||||
if Instant::now() >= self.next_frame {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
Input {
|
||||
gamepads: self.gamepads,
|
||||
reset,
|
||||
@@ -227,10 +243,12 @@ impl WindowImpl for Window {
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
self.filter.render(&mut render_pass);
|
||||
@@ -354,7 +372,7 @@ impl PaletteScreenMode {
|
||||
format: wgpu::TextureFormat::R8Uint,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
label: None,
|
||||
view_formats: &[]
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let palette_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
@@ -369,7 +387,7 @@ impl PaletteScreenMode {
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
label: None,
|
||||
view_formats: &[]
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let screen_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
@@ -384,7 +402,7 @@ impl PaletteScreenMode {
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
label: None,
|
||||
view_formats: &[]
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let framebuffer_texture_view =
|
||||
@@ -491,7 +509,7 @@ impl PaletteScreenMode {
|
||||
&bytemuck::cast_slice(pixels),
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: NonZeroU32::new(320),
|
||||
bytes_per_row: Some(320),
|
||||
rows_per_image: None,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
@@ -513,7 +531,7 @@ impl PaletteScreenMode {
|
||||
&bytemuck::cast_slice(palette),
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: NonZeroU32::new(256 * 4),
|
||||
bytes_per_row: Some(256 * 4),
|
||||
rows_per_image: None,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
@@ -532,10 +550,12 @@ impl PaletteScreenMode {
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
|
||||
@@ -15,7 +15,7 @@ struct FpsCounter {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(config: WindowConfig) -> Result<Window> {
|
||||
pub fn new(mut config: WindowConfig) -> Result<Window> {
|
||||
let fps_counter = if config.fps_counter {
|
||||
Some(FpsCounter {
|
||||
start: Instant::now(),
|
||||
@@ -24,6 +24,7 @@ impl Window {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
config.scale = config.scale.max(1.).min(20.);
|
||||
if config.enable_gpu {
|
||||
match gpu::Window::new(config) {
|
||||
Ok(window) => {
|
||||
@@ -71,6 +72,7 @@ pub struct WindowConfig {
|
||||
filter: u32,
|
||||
fullscreen: bool,
|
||||
fps_counter: bool,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl Default for WindowConfig {
|
||||
@@ -80,6 +82,7 @@ impl Default for WindowConfig {
|
||||
filter: 5,
|
||||
fullscreen: false,
|
||||
fps_counter: false,
|
||||
scale: 2.,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +105,10 @@ impl WindowConfig {
|
||||
}
|
||||
self.fullscreen = args.contains("--fullscreen");
|
||||
self.fps_counter = args.contains("--fps");
|
||||
self.scale = args
|
||||
.opt_value_from_str("--scale")
|
||||
.unwrap()
|
||||
.unwrap_or(self.scale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user