Search code examples
javascriptrustwebassemblywasm-bindgenwebgpu

WebGPU Rust and Javascript communication


I incorporate WebGPU+Rust in the following way

(index.html)

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GreenMatterAI graphics preview</title>
    <style>
        canvas {
            background-color: black;
        }
    </style>
</head>

<body id="wasm-example">
<script type="module">
      import init from "./pkg/gmai_cad.js";
      init().then(() => {
          console.log("WASM Loaded");
      });
  </script>
</body>

</html>

(lib.rs)

#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
pub async fn run() {
    cfg_if::cfg_if! {
        if #[cfg(target_arch = "wasm32")] {
            std::panic::set_hook(Box::new(console_error_panic_hook::hook));
            console_log::init_with_level(log::Level::Warn).expect("Couldn't initialize logger");
        } else {
            env_logger::init();
        }
    }

    let event_loop = EventLoop::new();
    let window = WindowBuilder::new().build(&event_loop).unwrap();
    #[cfg(target_arch = "wasm32")]
    {
        use winit::dpi::PhysicalSize;
        window.set_inner_size(PhysicalSize::new(450, 400));

        use winit::platform::web::WindowExtWebSys;
        web_sys::window()
            .and_then(|win| win.document())
            .and_then(|doc| {
                let dst = doc.get_element_by_id("wasm-example")?;
                let canvas = web_sys::Element::from(window.canvas());
                dst.append_child(&canvas).ok()?;
                Some(())
            })
            .expect("Couldn't append canvas to document body.");
    }
    let mut state = State::new(window).await;
    event_loop.run(move |event, _, control_flow| match event {
        Event::RedrawRequested(window_id) if window_id == state.window().id() => {
            state.update();
            state.render()
        }
        Event::MainEventsCleared => {
            state.window().request_redraw();
        }
        Event::WindowEvent {
            ref event,
            window_id,
        } => { state.event(event) },
        _ => {}
    });
}

The way this code is structured there is no way I could pass any data from Javascript to the render loop. I want to have some HTML inputs that user can modify and affect the rendering interactively. The only thing that comes to my mind is to have a global mutable variable and expose a few Rust functions that Javascript could use to modify those variables. Of course that's a clear Rust anti-pattern. What's the canonical way of going about this?


Solution

  • Instead of using a function marked #[wasm_bindgen(start)], use an ordinary #[wasm_bindgen] exported function that can take parameters, and call it from JavaScript.

    #[cfg(target_arch = "wasm32")
    #[wasm_bindgen]
    pub async fn run_in_browser(configuration: JsValue /* or whatever */) {
        ...
    }
    
    <script type="module">
        import init, { run_in_browser } from "./pkg/gmai_cad.js";
        init().then(() => {
            console.log("WASM Loaded");
            run_in_browser({ /* put configuration/callbacks here */ });
        });
    </script>