feat: web build via trunk with WebGPU detection + fallback

This commit is contained in:
xfy 2026-07-10 10:12:57 +08:00
parent 25e2207e0d
commit 9c8f384d0e
8 changed files with 101 additions and 1 deletions

5
.cargo/config.toml Normal file
View File

@ -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"]

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
/dist

1
Cargo.lock generated
View File

@ -4910,6 +4910,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_egui", "bevy_egui",
"js-sys",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]

View File

@ -11,6 +11,7 @@ bevy_egui = "0.41"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["Gpu", "Navigator", "Window", "Document", "HtmlElement", "Element"] } web-sys = { version = "0.3", features = ["Gpu", "Navigator", "Window", "Document", "HtmlElement", "Element"] }
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
js-sys = "0.3"
# Smaller wasm binary in release web builds (per Bevy examples README). # Smaller wasm binary in release web builds (per Bevy examples README).
[profile.wasm-release] [profile.wasm-release]

8
Trunk.toml Normal file
View File

@ -0,0 +1,8 @@
[build]
target = "web/index.html"
dist = "dist"
[serve]
address = "127.0.0.1"
port = 8080
open = false

View File

@ -1,8 +1,29 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy::window::WindowPlugin;
#[cfg(target_arch = "wasm32")]
mod web;
fn main() { 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() 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) .add_systems(Startup, setup)
.run(); .run();
} }

42
src/web.rs Normal file
View File

@ -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<Gpu>`), 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::<web_sys::HtmlElement>();
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();
}
}
}
}

21
web/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>singularity-rs</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #000; }
canvas { width: 100vw; height: 100vh; display: block; }
</style>
</head>
<body>
<link
data-trunk rel="rust"
href="../Cargo.toml"
data-bin="singularity-rs"
data-type="main"
data-cargo-features="bevy/webgpu"
data-wasm-opt="z"
/>
</body>
</html>