diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a70c556 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +# WebGPU bindings in web-sys are gated behind `web_sys_unstable_apis`. +# Enable that cfg when building for the web target only (harmless on desktop, +# where src/web.rs is cfg'd out anyway). +[target.'cfg(target_arch = "wasm32")'] +rustflags = ["--cfg", "web_sys_unstable_apis"] diff --git a/.gitignore b/.gitignore index ea8c4bf..4f96631 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/dist diff --git a/Cargo.lock b/Cargo.lock index 0dbf10f..86d9293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4910,6 +4910,7 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_egui", + "js-sys", "wasm-bindgen", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 5ef4396..eb471ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ bevy_egui = "0.41" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features = ["Gpu", "Navigator", "Window", "Document", "HtmlElement", "Element"] } wasm-bindgen = "0.2" +js-sys = "0.3" # Smaller wasm binary in release web builds (per Bevy examples README). [profile.wasm-release] diff --git a/Trunk.toml b/Trunk.toml new file mode 100644 index 0000000..7c41502 --- /dev/null +++ b/Trunk.toml @@ -0,0 +1,8 @@ +[build] +target = "web/index.html" +dist = "dist" + +[serve] +address = "127.0.0.1" +port = 8080 +open = false diff --git a/src/main.rs b/src/main.rs index 8d9b73f..c22d663 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,29 @@ use bevy::prelude::*; +use bevy::window::WindowPlugin; + +#[cfg(target_arch = "wasm32")] +mod web; fn main() { + // On web, abort startup if WebGPU isn't available and show a message. + #[cfg(target_arch = "wasm32")] + { + if !web::webgpu_available() { + web::show_fallback_message(); + return; + } + } + App::new() - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "singularity-rs".into(), + // On web, make the canvas track the browser window size. + fit_canvas_to_parent: true, + ..default() + }), + ..default() + })) .add_systems(Startup, setup) .run(); } diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 0000000..c07c56d --- /dev/null +++ b/src/web.rs @@ -0,0 +1,42 @@ +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; + +#[cfg(target_arch = "wasm32")] +pub fn webgpu_available() -> bool { + // `navigator.gpu` is gated in web-sys behind `web_sys_unstable_apis` and its + // getter returns `Gpu` directly (not `Option`), so we probe the JS + // object for the property instead. Reflect::has returns `true` when the + // browser exposes the WebGPU API. + web_sys::window() + .and_then(|w| { + let nav = w.navigator(); + js_sys::Reflect::has(nav.as_ref(), &"gpu".into()).ok() + }) + .unwrap_or(false) +} + +#[cfg(target_arch = "wasm32")] +pub fn show_fallback_message() { + if let Some(window) = web_sys::window() { + if let Some(document) = window.document() { + if let Some(body) = document.body() { + let el = document + .create_element("div") + .unwrap() + .unchecked_into::(); + el.set_inner_text( + "WebGPU is not available in this browser. \ + Please use a recent version of Chrome, Edge, or Firefox.", + ); + el.set_attribute( + "style", + "position:fixed;inset:0;display:flex;align-items:center;\ + justify-content:center;font-family:sans-serif;font-size:1.5rem;\ + text-align:center;padding:2rem;background:#111;color:#eee;", + ) + .ok(); + body.append_child(&el).ok(); + } + } + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..0867d3e --- /dev/null +++ b/web/index.html @@ -0,0 +1,21 @@ + + + + + singularity-rs + + + + + +