WebGPU (Dawn)
Electrobun can bundle a native WebGPU implementation (Dawn) and expose it to your Bun process. This lets you render with GPU-backed windows, run compute workloads, and integrate WebGPU-first libraries without a browser webview.
Enable Bundling
Enable WebGPU per platform in electrobun.config.ts:
// electrobun.config.ts
import { type ElectrobunConfig } from "electrobun";
export const config: ElectrobunConfig = {
build: {
macos: { bundleWGPU: true },
win: { bundleWGPU: true },
linux: { bundleWGPU: true },
},
}; When enabled, Electrobun bundles the Dawn dynamic library alongside your app so it is available at runtime.
Create A GPU Window
WebGPU rendering runs inside a GpuWindow (which owns a native WGPU-backed view). You can then create a WebGPU context from that window:
import { GpuWindow, webgpu } from "electrobun/bun";
const win = new GpuWindow({
title: "WebGPU",
frame: { width: 800, height: 600, x: 200, y: 120 },
});
// Create a WebGPU context bound to this window.
const ctx = webgpu.createContext(win);
const adapter = await webgpu.navigator.requestAdapter({
compatibleSurface: ctx,
});
const device = await adapter.requestDevice();
ctx.configure({
device,
format: webgpu.navigator.getPreferredCanvasFormat(),
alphaMode: "premultiplied",
}); <electrobun-wgpu> (GPU Views Inside Web UIs)
You can embed native GPU surfaces inside a webview layout using the <electrobun-wgpu> custom element. This is documented separately on the WGPU Tag page.
Read the <electrobun-wgpu> Tag docs →
Raw FFI Access
If you want direct access to Dawn's C API, use the raw FFI bindings:
import { WGPU } from "electrobun/bun";
if (!WGPU.native.available) {
throw new Error("WGPU not bundled or failed to load");
}
const instance = WGPU.native.symbols.wgpuCreateInstance(0);
// ...use Dawn C API via WGPU.native.symbols The raw FFI layer mirrors the Dawn C API and is useful for low-level control, custom bindings, or compute workloads.
Compute + Readback
Electrobun's WebGPU adapter also supports compute workloads from Bun. You can create compute pipelines, dispatch workgroups, and read results back into Bun-managed memory. This is how the wgpu-mlp template performs GPU inference while keeping a WebView UI.
Readback Example
For readback, write GPU output into a buffer with MAP_READ, then mapAsync and copy the data into a Bun-managed ArrayBuffer.
// After a compute pass writes into readbackBuffer
await readbackBuffer.mapAsync(GPUMapMode.READ);
const mapped = readbackBuffer.getMappedRange();
const out = new Uint8Array(mapped.slice(0));
readbackBuffer.unmap(); Bundling + Runtime Resolution
When bundleWGPU is enabled, Electrobun packages the Dawn dynamic library with your app. At runtime, the loader searches:
- The explicit path in
ELECTROBUN_WGPU_PATH(if set). - The current working directory.
- The executable’s
Resources/MacOSfolders on macOS.
If WGPU.native.available is false, confirm the library is bundled for that platform and check that the loader path is valid.
Examples + Templates
Reference implementations you can copy from:
wgpu: raw FFI rendering in a GPU window.wgpu-threejs: Three.js running on WebGPU.wgpu-babylon: Babylon.js WebGPU integration.wgpu-mlp: compute + readback for MLP inference.electrobun-doom: real game rendering via WGPU +GpuWindow.
Three.js Integration
Electrobun re-exports three for convenience. Use the WebGPU renderer with a simple canvas shim that proxies to the GPU window:
import { GpuWindow, three, webgpu } from "electrobun/bun";
const win = new GpuWindow({ title: "three.js + WebGPU" });
webgpu.install();
const size = win.getSize();
const canvas = {
width: size.width,
height: size.height,
clientWidth: size.width,
clientHeight: size.height,
style: {},
getContext: (type) => {
if (type !== "webgpu") return null;
return webgpu.createContext(win).context;
},
getBoundingClientRect: () => ({
left: 0,
top: 0,
width: win.getSize().width,
height: win.getSize().height,
}),
addEventListener: () => {},
removeEventListener: () => {},
setAttribute: () => {},
};
const renderer = new (three as any).WebGPURenderer({ canvas });
await renderer.init();
const scene = new three.Scene();
const camera = new three.PerspectiveCamera(60, size.width / size.height, 0.1, 100);
camera.position.z = 2;
const mesh = new three.Mesh(
new three.BoxGeometry(0.6, 0.6, 0.6),
new three.MeshStandardMaterial({ color: 0x202020 })
);
scene.add(mesh);
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}); Babylon.js Integration
Electrobun also re-exports @babylonjs/core. Use WebGPUEngine with the same canvas shim approach:
import { GpuWindow, babylon, webgpu } from "electrobun/bun";
const win = new GpuWindow({ title: "Babylon + WebGPU" });
webgpu.install();
const size = win.getSize();
const canvas = {
width: size.width,
height: size.height,
clientWidth: size.width,
clientHeight: size.height,
style: {},
getContext: (type) => {
if (type !== "webgpu") return null;
return webgpu.createContext(win).context;
},
getBoundingClientRect: () => ({
left: 0,
top: 0,
width: win.getSize().width,
height: win.getSize().height,
}),
addEventListener: () => {},
removeEventListener: () => {},
setAttribute: () => {},
};
const engine = new babylon.WebGPUEngine(canvas, { antialias: false });
await engine.initAsync();
const scene = new babylon.Scene(engine);
scene.clearColor = new babylon.Color4(0.12, 0.12, 0.14, 1);
const camera = new babylon.ArcRotateCamera(
"camera",
Math.PI / 4,
Math.PI / 3,
2.5,
new babylon.Vector3(0, 0, 0),
scene
);
camera.attachControl(canvas, true);
new babylon.HemisphericLight("light", new babylon.Vector3(0.4, 1, 0.6), scene);
const box = babylon.MeshBuilder.CreateBox("box", { size: 0.7 }, scene);
const mat = new babylon.StandardMaterial("mat", scene);
mat.diffuseColor = new babylon.Color3(0.12, 0.12, 0.12);
mat.specularColor = new babylon.Color3(0.4, 0.4, 0.5);
box.material = mat;
engine.runRenderLoop(() => scene.render());