This project compares several ways to render the Mandelbrot set in the browser:
- single-threaded TypeScript on a 2D canvas
- single-threaded Rust compiled to WebAssembly; rendered on a 2D canvas
- multi-threaded Rust compiled to WebAssembly with Rayon and
SharedArrayBuffer; rendered on a 2D canvas - WebGPU fragment-shader rendering
Mandelbrot rendering is a good benchmark for browser compute experiments because every pixel can be calculated independently, the amount of work grows quickly with canvas size and iteration count, and the output is easy to inspect visually. That makes it useful for comparing the practical overhead and benefit of TypeScript, Rust/WASM, browser threads, and GPU execution.
The main idea is to show where each layer helps:
- TypeScript is the baseline and the most straightforward path.
- Rust/WASM gives a compiled CPU path while keeping browser delivery.
- Rayon-backed WASM uses all available CPU cores through WebAssembly threads.
- WebGPU moves the same kind of per-pixel work onto the GPU.
The web app lives at the repo root; Rust/WASM is a separate workspace package:
src/,static/,webpack.config.cjs: Vue 3 + TypeScript Webpack app (UI, WebGPU renderer)rust/: Cargo crate (@mandelbrot/wasm) that exposes Mandelbrot render functions and the Rayon thread-pool initializer throughwasm-bindgen
The Rust build emits generated WASM bindings into rust/pkg/. The web app imports them via the @mandelbrot/wasm workspace dependency.
Install Rust using rustup (the official installer):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shAfter installation, restart your terminal or run:
source $HOME/.cargo/envVerify installation:
rustc --version
cargo --versionThe WASM build runs wasm-bindgen after cargo build. Install the CLI version that matches the crate:
cargo install wasm-bindgen-cli --version 0.2.108 --lockedVerify installation:
wasm-bindgen --versionpnpm install
pnpm dev
pnpm build
pnpm checkThe dev server sends these headers because WASM threads require SharedArrayBuffer:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
Production uses the same headers via vercel.json (copied into dist/ at build time).