Compare commits

...

1 Commits
master ... wasm

Author SHA1 Message Date
James T. Martin a0ae573258
Ported to WASM + WebGPU. (Untested.)
I was not able to test this because:

* Firefox nightly does not support the WebGPU spec of wgpu 0.13
* Chrome Dev WebGPU does not work consistently on AMD+Linux
* I don't feel like setting up a Windows VM or reverting wgpu

Pushing onto a separate branch for whenever Firefox gets updated,
so I can test it then.
2022-07-09 00:19:58 -07:00
13 changed files with 7868 additions and 129 deletions

View File

@ -7,3 +7,6 @@ charset = utf-8
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js, json, html}]
indent_size = 2

10
.gitignore vendored
View File

@ -11,3 +11,13 @@
!/Cargo.lock
!/Cargo.toml
!/LICENSE.txt
# www source code
!/www/bootstrap.js
!/www/index.js
!/www/index.html
# www configuration
!/www/package.json
!/www/package-lock.json
!/www/webpack.config.js

37
Cargo.lock generated
View File

@ -297,6 +297,26 @@ dependencies = [
"memchr",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "console_log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
dependencies = [
"log",
"web-sys",
]
[[package]]
name = "copyless"
version = "0.1.5"
@ -1179,11 +1199,16 @@ version = "0.1.0"
dependencies = [
"bytemuck",
"claxon",
"console_error_panic_hook",
"console_log",
"cpal",
"fern",
"image",
"log",
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu",
"winit",
]
@ -1434,18 +1459,6 @@ checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
dependencies = [
"once_cell",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]

View File

@ -6,13 +6,14 @@ description = ""
repository = "https://github.com/jamestmartin/pathland"
license = "0BSD"
publish = false
autobins = false
[features]
client = []
[lib]
crate-type = ["cdylib", "rlib"]
server = []
[dependencies]
[[bin]]
name = "pathland"
path = "src/main.rs"
# brotli (compression format)
#[dependencies.brotli]
@ -39,11 +40,6 @@ version = "0.13.5"
#[dependencies.directories]
#version = "4.0"
# logging backend
[dependencies.fern]
version = "0.6.1"
features = ["colored"]
# text rendering
#[dependencies.fontdue]
#version = "0.7.2"
@ -85,12 +81,6 @@ features = ["std"]
#[dependencies.serde]
#version = "1.0"
# async runtime
[dependencies.tokio]
version = "1.19"
# TODO: Is rt-multi-thread faster for our use case?
features = ["rt", "macros"]
# TOML (configuration format)
#[dependencies.toml_edit]
#version = "0.14.4"
@ -104,7 +94,26 @@ version = "0.13.1"
version = "0.26.1"
features = ["x11", "wayland"]
# logging backend
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.fern]
version = "0.6.1"
features = ["colored"]
# async runtime
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1.19"
# TODO: Is rt-multi-thread faster for our use case?
features = ["rt"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.81"
wasm-bindgen-futures = "0.4.31"
web-sys = { version = "0.3.58", features = ["HtmlCanvasElement"] }
[profile.release]
strip = "symbols"
lto = "fat"
codegen-units = 1
opt-level = "s"

View File

@ -159,7 +159,7 @@ impl Graphics {
VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as BufferAddress,
step_mode: VertexStepMode::Vertex,
attributes: &vertex_attr_array![0 => Float32x3, 1 => Float32x3]
attributes: &vertex_attr_array![0 => Float32x2]
}
]
},
@ -224,7 +224,7 @@ impl Graphics {
format: self.surface_format,
width: size.width,
height: size.height,
present_mode: PresentMode::Mailbox
present_mode: PresentMode::AutoVsync
});
self.uniform_copy_buffer.slice(..).get_mapped_range_mut().copy_from_slice(bytemuck::cast_slice(&[Uniforms {
dimensions: [self.desired_size.width as f32, self.desired_size.height as f32],
@ -235,7 +235,11 @@ impl Graphics {
let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor::default());
encoder.copy_buffer_to_buffer(&self.uniform_copy_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as u64);
self.queue.submit(std::iter::once(encoder.finish()));
self.uniform_copy_buffer.slice(..).map_async(MapMode::Write, |err| err.unwrap());
self.uniform_copy_buffer.slice(..).map_async(MapMode::Write, |err| {
if let Err(err) = err {
log::error!("buffer async error: {}", err);
}
});
}
fn reconfigure_surface_if_stale(&mut self) {

122
src/lib.rs Normal file
View File

@ -0,0 +1,122 @@
mod audio;
mod graphics;
use winit::window::WindowBuilder;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn main(canvas: web_sys::HtmlCanvasElement) {
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(_main(move |wb| {
log::info!("callback");
use winit::platform::web::WindowBuilderExtWebSys;
wb.with_canvas(Some(canvas))
}));
}
pub async fn _main<F>(wb_platform_specific: F)
where F: FnOnce(WindowBuilder) -> WindowBuilder
{
setup_logger();
log::info!("main");
use winit::event_loop::EventLoop;
// TODO: class and app id on unix
//use winit::platform::unix::WindowBuilderExtUnix;
let event_loop = EventLoop::new();
let window = wb_platform_specific(WindowBuilder::new())
// Arbitrarily chosen as the minimum resolution the game is designed to support (for e.g. UI scaling).
.with_min_inner_size(winit::dpi::LogicalSize { height: 360, width: 640 })
.with_title("Pathland")
.with_maximized(true)
// TODO: hide window until first frame is drawn (default behavior on wayland)
.with_visible(true)
.with_decorations(true)
.build(&event_loop)
.expect("Failed to create window.");
// TODO: window icon, fullscreen, IME position, cursor grab, cursor visibility
let mut graphics = graphics::Graphics::setup(window).await;
//let audio = audio::Audio::setup();
event_loop.run(move |event, target, control_flow| {
use winit::event::*;
*control_flow = winit::event_loop::ControlFlow::Wait;
match event {
Event::WindowEvent { window_id, event } => {
match event {
WindowEvent::CloseRequested => {
std::process::exit(0);
},
WindowEvent::Destroyed => {
std::process::exit(0);
},
WindowEvent::Focused(focused) => {
// TODO: handle focus/unfocus (e.g. pause, resume)
},
WindowEvent::Resized(new_size) => {
graphics.window_resized(new_size)
},
WindowEvent::ScaleFactorChanged { new_inner_size: new_size, .. } => {
graphics.window_resized(*new_size)
},
// TODO: handle user input
_ => {}
}
},
Event::DeviceEvent { device_id, event } => {
// TODO: handle user input
},
Event::MainEventsCleared => {
// TODO: main event loop. queue simulation calculations, screen redrawing, etc.
},
Event::RedrawRequested(_) => {
graphics.draw();
},
Event::LoopDestroyed => {
std::process::exit(0);
},
_ => {}
}
// TODO: What is suspending/resuming? Do I want to support it?
});
}
#[cfg(not(target_arch = "wasm32"))]
fn setup_logger() {
use fern::Dispatch;
use fern::colors::ColoredLevelConfig;
use log::LevelFilter;
Dispatch::new()
.chain(
Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{}] {}",
ColoredLevelConfig::default().color(record.level()),
message
));
})
.level(LevelFilter::Warn)
.level_for("pathland", LevelFilter::Info)
.chain(std::io::stderr()))
.chain(
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{}] {}",
record.level(),
message
))
})
.level(LevelFilter::Debug)
.level_for("pathland", LevelFilter::Trace)
// FIXME: linux-specific path
.chain(std::fs::OpenOptions::new().write(true).create(true).truncate(true).open("/tmp/pathland.log").unwrap()))
.apply().unwrap();
}
#[cfg(target_arch = "wasm32")]
fn setup_logger() {
console_log::init().unwrap();
}

View File

@ -1,107 +1,19 @@
mod audio;
mod graphics;
use winit::window::WindowBuilder;
use std::sync::{Arc, RwLock};
fn main() {
tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(pathland::_main(wb_platform_specific));
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
setup_logger();
use winit::event_loop::EventLoop;
#[cfg(unix)]
fn wb_platform_specific(wb: WindowBuilder) -> WindowBuilder {
use winit::platform::unix::WindowBuilderExtUnix;
let event_loop = EventLoop::new();
let now = std::time::Instant::now();
let window = winit::window::WindowBuilder::new()
// Arbitrarily chosen as the minimum resolution the game is designed to support (for e.g. UI scaling).
.with_min_inner_size(winit::dpi::LogicalSize { height: 360, width: 640 })
.with_title("Pathland")
.with_maximized(true)
// TODO: hide window until first frame is drawn (default behavior on wayland)
.with_visible(true)
.with_decorations(true)
wb
.with_class("pathland".to_string(), "pathland".to_string())
.with_app_id("pathland".to_string())
.build(&event_loop)
.expect("Failed to create window.");
// TODO: window icon, fullscreen, IME position, cursor grab, cursor visibility
let mut graphics = graphics::Graphics::setup(window).await;
//let audio = audio::Audio::setup();
log::info!("Took {} milliseconds", now.elapsed().as_millis());
event_loop.run(move |event, target, control_flow| {
use winit::event::*;
*control_flow = winit::event_loop::ControlFlow::Wait;
match event {
Event::WindowEvent { window_id, event } => {
match event {
WindowEvent::CloseRequested => {
std::process::exit(0);
},
WindowEvent::Destroyed => {
std::process::exit(0);
},
WindowEvent::Focused(focused) => {
// TODO: handle focus/unfocus (e.g. pause, resume)
},
WindowEvent::Resized(new_size) => {
graphics.window_resized(new_size)
},
WindowEvent::ScaleFactorChanged { new_inner_size: new_size, .. } => {
graphics.window_resized(*new_size)
},
// TODO: handle user input
_ => {}
}
},
Event::DeviceEvent { device_id, event } => {
// TODO: handle user input
},
Event::MainEventsCleared => {
// TODO: main event loop. queue simulation calculations, screen redrawing, etc.
},
Event::RedrawRequested(_) => {
graphics.draw();
},
Event::LoopDestroyed => {
std::process::exit(0);
},
_ => {}
}
// TODO: What is suspending/resuming? Do I want to support it?
});
}
fn setup_logger() {
use fern::Dispatch;
use fern::colors::ColoredLevelConfig;
use log::LevelFilter;
Dispatch::new()
.chain(
Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{}] {}",
ColoredLevelConfig::default().color(record.level()),
message
));
})
.level(LevelFilter::Warn)
.level_for("pathland", LevelFilter::Info)
.chain(std::io::stderr()))
.chain(
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{}] {}",
record.level(),
message
))
})
.level(LevelFilter::Debug)
.level_for("pathland", LevelFilter::Trace)
.chain(std::fs::OpenOptions::new().write(true).create(true).truncate(true).open("/tmp/pathland.log").unwrap()))
.apply().unwrap();
#[cfg(not(unix))]
fn wb_platform_specific(wb: WindowBuilder) -> WindowBuilder {
wb
}

5
www/bootstrap.js Normal file
View File

@ -0,0 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));

7
www/index.html Normal file
View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<title>pathland</title>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<canvas id="canvas"></canvas>
<script src="bootstrap.js"></script>

4
www/index.js Normal file
View File

@ -0,0 +1,4 @@
import * as pathland from "pathland";
let canvas = document.getElementById("canvas");
pathland.main(document.getElementById("canvas"));

7604
www/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
www/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "pathland",
"version": "0.1.0",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jamestmartin/pathland"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"dependencies": {
"pathland": "file:../pkg"
},
"devDependencies": {
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"copy-webpack-plugin": "^5.1.1"
}
}

18
www/webpack.config.js Normal file
View File

@ -0,0 +1,18 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require('path');
module.exports = {
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bootstrap.js",
hashFunction: "xxhash64",
},
mode: "development",
plugins: [
new CopyWebpackPlugin(['index.html'])
],
experiments: {
syncWebAssembly: true
}
};