Skip to content

spencerkohan/minimal-wgpu-react-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Minimal WGPU / React Integration Example

This repo is intended to demonstrate a minimal example of integrating a wasm module implemented in Rust to display webgpu content in a React webapp.

Motivation

Recently I wanted to integrate a shader-based simulation in a website, and while the wgpu repo has a ton of amazing examples, I found it to be somewhat obfuscated how the wgpu modues in the example code are actually integrated into a website, due to the fact that in that project, there is a script which builds the wasm, generates the necessary artifacts and website, and the example site and wasm module contain some extra logic to select the relevant example for display. It took a bit of digging to figure out how the pieces fit together, and I wanted to save others the work of figuring out how to build a wgpu/wasm module in Rust and display it on a website.

The fact that we can display rich, interactive content implemented in Rust on the web is awesome, and I want this to be more accessible.

Project Layout

This project contains a couple of components:

  • The wgpu_module crate contains the rust implementation of the wgpu code which will be compiled to a wasm module for display on the site. It renders a simple triangle using a single wgpu shader.

  • The site dir contains a simple Vite React application which displays the content rendered by the wgpu module.

  • The run.sh script handles the process of building the module, generating the necessary artifacts and running the vite application.

Dependencies

In order to run this project, a few dependencies are needed:

1. Yarn

To run the Vite applicaiton, you will need to have yarn installed. Instructions can be found here.

2. Rust Dependencies

You will also need the target wasm32-unknown-unknown in your rust toolchain, and to have the wasm-bindgen-cli installed. This can be achieved by running the following commands:

$ rustup target add wasm32-unknown-unknown
$ cargo install wasm-bindgen-cli

Usage

To build and run the example in the webapp, simply call the run.sh script:

$ /bin/bash run.sh

It's also possible to run the wgpu example as a standalone native app like so:

$ cargo run -p wgpu_module

How it Works

In order to display wgpu content on the web, the follwing is needed:

  1. A wasm module containing the wgpu implementation.
  2. A javascript binding, to allow your web front-end application to communicate with the wasm module.
  3. A website with a canvas to display the wgpu content.

We can achieve this with the following steps:

1. Build the wasm module

The first step is to build our rust crate as a wasm module. For this we use cargo with the wasm target selected:

cargo build --target wasm32-unknown-unknown -p wgpu_module

This will generate the file wgpu_module.wasm in the directory ./target/wasm32-unknown-unknown/debug. We're using debug for simplicity, but we could do a release build as well.

2. Generate the js bindings

Once we have our wasm module built, we need to generate the js bindings. This is achieved with the following command:

wasm-bindgen target/wasm32-unknown-unknown/debug/wgpu_module.wasm --target web --out-dir site/src/generated --out-name wgpu_module

This will generate the js bindings and place them in the src directory of our React project.

The site project is using typescript, so I'm generating typescript bindings, but if you want pure js you can use the --no-typescript option on the bindgen command.

3. Call the module from the React component

Now that everything is in place, the last thing we need to do is call the module we created from our react front-end code. We do this inside site/src/App.tsx.

First we need to import the module:

import wgpu_module from "./generated/wgpu_module";

This imports the typescript module we generated with bindgen.

Next we need to call the module:

function App() {
  useEffect(() => {
    wgpu_module(); // Calls `__wbg_init()` from wasm-bindgen
  }, []);

  ...
}

This will call the default function from our generated typescript module.

To understand what this is doing, we can look at our main function inside wgpu_module/src/lib.rs:

First we create an event loop:

 let event_loop = EventLoop::new().unwrap();

Then we create a window object, bound to our html canvas element:

let mut builder = winit::window::WindowBuilder::new();
#[cfg(target_arch = "wasm32")]
{
    use wasm_bindgen::JsCast;
    use winit::platform::web::WindowBuilderExtWebSys;
    let canvas = web_sys::window()
        .unwrap()
        .document()
        .unwrap()
        .get_element_by_id("canvas")
        .unwrap()
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .unwrap();
    builder = builder.with_canvas(Some(canvas));
}
let window = builder.build(&event_loop).unwrap();

Notice we query the element by id with the call .get_element_by_id("canvas"), so this requires that we have a canvas element with the correct id in our site:

<canvas id="canvas"/>

Finally, we spawn our task to render to the window with the event loop we created:

wasm_bindgen_futures::spawn_local(run(event_loop, window));

This will call our run function, which renders to the window using our shader whenever a resize event, or redraw event is detected.

About

A minimal example for integrating a wgpu module implemented in Rust into a React application

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published