Overview
Add WebAssembly (WASM) build support to lambda-rs, enabling the engine's rendering examples and demos to compile and run in web browsers. This feature would allow users to experience lambda-rs demos directly in a browser without local installation, improving accessibility and enabling web-based applications built with the engine. WASM support would leverage wgpu's WebGPU backend to provide GPU-accelerated rendering in compatible browsers.
Current State
The engine currently targets native desktop platforms only. All rendering examples (triangle.rs, textured_quad.rs, instanced_quads.rs, etc.) require compilation to native binaries and cannot run in web environments.
The underlying dependencies (wgpu, winit) already support WASM targets, but lambda-rs-platform uses blocking patterns that are incompatible with WASM:
Blocking GPU Initialization (pollster::block_on):
// crates/lambda-rs-platform/src/wgpu/instance.rs:209block_on(self.instance.request_adapter(options))// crates/lambda-rs-platform/src/wgpu/gpu.rs:157
let (device, queue) = block_on(adapter.request_device(&descriptor))?;
Blocking Event Loop:
// crates/lambda-rs-platform/src/winit/mod.rs:237self.event_loop.run(move |event, target| { ...})On WASM targets, pollster::block_on() panics because browsers do not permit blocking the main thread. Similarly, EventLoop::run() cannot block in the browser environment.
Scope
Goals:
- Enable
wasm32-unknown-unknown target compilation for lambda-rs and lambda-rs-platform - Ensure rendering examples compile and run in browsers with WebGPU support
- Provide build tooling and documentation for WASM builds
- Abstract platform-specific initialization (canvas binding, event loop differences) in lambda-rs-platform
- Create a web hosting setup (e.g., wasm-pack, trunk, or similar) for running demos
Non-Goals:
- Audio support in WASM (can be addressed in a separate feature)
- Full feature parity with native builds in the initial implementation
- Support for browsers without WebGPU (WebGL fallback)
- Production-optimized WASM bundle sizes (optimization can follow)
Proposed API
The public API should remain largely unchanged. The existing ApplicationRuntimeBuilder pattern and Component trait would continue to work, with platform-specific initialization handled internally via conditional compilation in lambda-rs-platform.
Existing Application Pattern (unchanged):
// Current pattern used in examples like triangle.rs, textured_quad.rs, etc.fnmain(){let runtime = ApplicationRuntimeBuilder::new("2D Triangle Demo").with_renderer_configured_as(move |render_context_builder| {return render_context_builder.with_render_timeout(1_000_000_000);}).with_window_configured_as(move |window_builder| {return window_builder
.with_dimensions(1200,600).with_name("2D Triangle Window");}).with_component(move |runtime,demo:DemoComponent| {return(runtime, demo);}).build();start_runtime(runtime);}Build Configuration (Cargo.toml additions):
[target.'cfg(target_arch="wasm32")'.dependencies]
wasm-bindgen = "0.2"wasm-bindgen-futures = "0.4"web-sys = { version = "0.3", features = ["Document", "Window", "Element", "HtmlCanvasElement"] }
console_error_panic_hook = "0.1"console_log = "1.0"
[lib]
crate-type = ["cdylib", "rlib"]Platform Abstraction Changes (lambda-rs-platform):
WindowBuilder must support canvas binding on WASM targetsstart_runtime must handle async event loop requirements on web- Surface creation in GPU initialization must use canvas elements instead of native windows
// crates/lambda-rs-platform/src/windowing/mod.rs#[cfg(target_arch = "wasm32")]implWindowBuilder{/// Bind to an existing HTML canvas element by ID for WASM targets.pubfnwith_canvas_id(mutself,canvas_id:&str) -> Self;}Async/Sync Abstraction (lambda-rs-platform internal):
GPU initialization must be conditionally compiled to use blocking calls on native and async on WASM:
// crates/lambda-rs-platform/src/wgpu/instance.rs#[cfg(not(target_arch = "wasm32"))]pub(crate)fnrequest_adapter(&self,options:&wgpu::RequestAdapterOptions<'_,'_>,) -> Result<wgpu::Adapter, wgpu::RequestAdapterError>{
pollster::block_on(self.instance.request_adapter(options))}#[cfg(target_arch = "wasm32")]pub(crate)asyncfnrequest_adapter_async(&self,options:&wgpu::RequestAdapterOptions<'_,'_>,) -> Result<wgpu::Adapter, wgpu::RequestAdapterError>{self.instance.request_adapter(options).await}// crates/lambda-rs-platform/src/wgpu/gpu.rs#[cfg(not(target_arch = "wasm32"))]pubfnbuild(self,adapter:&wgpu::Adapter) -> Result<Gpu,GpuBuildError>{let(device, queue) = pollster::block_on(adapter.request_device(&descriptor))?;// ...}#[cfg(target_arch = "wasm32")]pubasyncfnbuild_async(self,adapter:&wgpu::Adapter) -> Result<Gpu,GpuBuildError>{let(device, queue) = adapter.request_device(&descriptor).await?;// ...}Event Loop Handling (lambda-rs-platform internal):
The event loop must use EventLoop::spawn() on WASM instead of EventLoop::run():
// crates/lambda-rs-platform/src/winit/mod.rs#[cfg(not(target_arch = "wasm32"))]pubfnrun_forever<Callback>(self,mutcallback:Callback)whereCallback:'static + FnMut(Event<E>,&EventLoopWindowTarget<E>),{self.event_loop.run(move |event, target| {
target.set_control_flow(ControlFlow::Poll);callback(event, target);}).expect("Event loop terminated unexpectedly");}#[cfg(target_arch = "wasm32")]pubfnrun_forever<Callback>(self,callback:Callback)whereCallback:'static + FnMut(Event<E>,&EventLoopWindowTarget<E>),{// spawn() does not block and integrates with browser's requestAnimationFrameself.event_loop.spawn(callback);}WASM Entry Point (example adaptation):
// examples/triangle.rs - conditional compilation for WASM entry#[cfg(target_arch = "wasm32")]use wasm_bindgen::prelude::*;#[cfg(target_arch = "wasm32")]#[wasm_bindgen(start)]pubfnwasm_main(){
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Info).expect("Logger init failed");main();}#[cfg(not(target_arch = "wasm32"))]fnmain(){// existing main code}#[cfg(target_arch = "wasm32")]fnmain(){let runtime = ApplicationRuntimeBuilder::new("2D Triangle Demo").with_window_configured_as(move |window_builder| {return window_builder.with_canvas_id("lambda-canvas");}).with_component(move |runtime,demo:DemoComponent| {return(runtime, demo);}).build();start_runtime(runtime);}Acceptance Criteria
Compilation:
Platform Abstraction:
Examples:
Tooling & Documentation:
Affected Crates
lambda-rs, lambda-rs-platform
Notes
- Browser Requirements: WebGPU is required; Chrome 113+, Firefox 121+, and Safari 18+ have support
- Related Dependencies: wgpu 26.x supports WASM via WebGPU; winit 0.29.x supports web targets
- Testing: Manual browser testing required; consider adding Playwright or similar for automated web testing in the future
Architectural Considerations:
| Concern | Native | WASM |
|---|
| GPU adapter request | pollster::block_on() | wasm_bindgen_futures::spawn_local() + async |
| GPU device request | pollster::block_on() | async .await |
| Event loop | EventLoop::run() (blocking) | EventLoop::spawn() (non-blocking) |
| Window surface | Native window handle | HTML canvas element |
| Logging | lambda-rs-logging | console_log crate |
- The public
lambda-rs API should remain synchronous-looking; async complexity is encapsulated in lambda-rs-platform - Use
#[cfg(target_arch = "wasm32")] and #[cfg(not(target_arch = "wasm32"))] for platform-specific code paths - On WASM, runtime initialization must be wrapped in
wasm_bindgen_futures::spawn_local() to execute async GPU setup - Consider creating internal helper functions/macros to reduce duplication between sync and async code paths
Future Work:
- WebGL2 fallback for broader browser support (separate feature)
- WASM audio support via Web Audio API (separate feature)
- Bundle size optimization with wasm-opt
- Hosting demos on GitHub Pages or similar
- Potential async public API if use cases demand it (would be a breaking change)
Platform Considerations:
- Event loop handling differs on web (cannot block the main thread)
- File/asset loading requires fetch API or embedding
- Window resizing and fullscreen require web-specific handling
- Panic hooks should use
console_error_panic_hook for browser debugging
Overview
Add WebAssembly (WASM) build support to lambda-rs, enabling the engine's rendering examples and demos to compile and run in web browsers. This feature would allow users to experience lambda-rs demos directly in a browser without local installation, improving accessibility and enabling web-based applications built with the engine. WASM support would leverage wgpu's WebGPU backend to provide GPU-accelerated rendering in compatible browsers.
Current State
The engine currently targets native desktop platforms only. All rendering examples (
triangle.rs,textured_quad.rs,instanced_quads.rs, etc.) require compilation to native binaries and cannot run in web environments.The underlying dependencies (wgpu, winit) already support WASM targets, but lambda-rs-platform uses blocking patterns that are incompatible with WASM:
Blocking GPU Initialization (
pollster::block_on):Blocking Event Loop:
On WASM targets,
pollster::block_on()panics because browsers do not permit blocking the main thread. Similarly,EventLoop::run()cannot block in the browser environment.Scope
Goals:
wasm32-unknown-unknowntarget compilation for lambda-rs and lambda-rs-platformNon-Goals:
Proposed API
The public API should remain largely unchanged. The existing
ApplicationRuntimeBuilderpattern andComponenttrait would continue to work, with platform-specific initialization handled internally via conditional compilation inlambda-rs-platform.Existing Application Pattern (unchanged):
Build Configuration (Cargo.toml additions):
Platform Abstraction Changes (lambda-rs-platform):
WindowBuildermust support canvas binding on WASM targetsstart_runtimemust handle async event loop requirements on webAsync/Sync Abstraction (lambda-rs-platform internal):
GPU initialization must be conditionally compiled to use blocking calls on native and async on WASM:
Event Loop Handling (lambda-rs-platform internal):
The event loop must use
EventLoop::spawn()on WASM instead ofEventLoop::run():WASM Entry Point (example adaptation):
Acceptance Criteria
Compilation:
cargo build --target wasm32-unknown-unknown -p lambda-rscompiles successfullycargo build --target wasm32-unknown-unknown -p lambda-rs-platformcompiles successfullyPlatform Abstraction:
pollster::block_onreplaced with conditional compilation (native: sync, WASM: async)EventLoop::run()replaced withEventLoop::spawn()on WASM targetsWindowBuildersupports canvas binding viawith_canvas_id()on WASMExamples:
triangleexample renders correctly in a WebGPU-enabled browsertextured_quadexample renders correctly with texture loadinginstanced_quadsexample demonstrates instanced rendering in browserTooling & Documentation:
scripts/build_wasm.sh)docs/explaining WASM build process and architecturedocs/features.mdupdated with WASM-related feature flagsAffected Crates
lambda-rs, lambda-rs-platform
Notes
Architectural Considerations:
pollster::block_on()wasm_bindgen_futures::spawn_local()+ asyncpollster::block_on().awaitEventLoop::run()(blocking)EventLoop::spawn()(non-blocking)lambda-rs-loggingconsole_logcratelambda-rsAPI should remain synchronous-looking; async complexity is encapsulated inlambda-rs-platform#[cfg(target_arch = "wasm32")]and#[cfg(not(target_arch = "wasm32"))]for platform-specific code pathswasm_bindgen_futures::spawn_local()to execute async GPU setupFuture Work:
Platform Considerations:
console_error_panic_hookfor browser debugging