| name | electrobun-webgpu |
| description | This skill should be used when the user asks about "electrobun WebGPU", "GpuWindow", "WGPUView", "WGSL shaders", "KEEPALIVE pattern", "electrobun GPU", render loops in electrobun, FFI pointer management, or GPU buffer serialization. |
| version | 1.0.0 |
Electrobun WebGPU Patterns
Electrobun wraps WGPU (a Rust WebGPU abstraction) via Bun FFI. GPU windows bypass the webview entirely — they render directly to a native surface.
Config Requirement
Always required before WebGPU code will work:
mac: { bundleWGPU: true },
win: { bundleWGPU: true },
linux: { bundleWGPU: true },
GpuWindow Setup
import { GpuWindow } from "electrobun/bun";
const gpuWin = new GpuWindow({
title: "GPU App",
frame: { width: 800, height: 600 },
centered: true,
});
const view = gpuWin.createView();
KEEPALIVE — Critical Pattern
Bun's GC will collect FFI pointers unless you hold a reference to them. Without KEEPALIVE, your app will crash mid-render with a segfault.
const KEEPALIVE: unknown[] = [];
const adapter = await navigator.gpu.requestAdapter();
KEEPALIVE.push(adapter);
const device = await adapter.requestDevice();
KEEPALIVE.push(device);
const pipeline = device.createRenderPipeline({ });
KEEPALIVE.push(pipeline);
const buffer = device.createBuffer({ });
KEEPALIVE.push(buffer);
Render Loop Pattern
const FRAME_MS = 16;
function renderFrame() {
const data = new ArrayBuffer(32);
const view = new DataView(data);
view.setFloat32(0, performance.now() / 1000, true);
view.setFloat32(4, canvas.width, true);
view.setFloat32(8, canvas.height, true);
view.setFloat32(12, mouseX, true);
view.setFloat32(16, mouseY, true);
device.queue.writeBuffer(uniformBuffer, 0, data);
const encoder = device.createCommandEncoder();
KEEPALIVE.push(encoder);
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: "clear",
storeOp: "store",
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
}
setInterval(renderFrame, FRAME_MS);
WGSL Shader Structure
// Vertex shader
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
}
@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOutput {
// Full-screen triangle
var positions = array<vec2f, 3>(
vec2f(-1.0, -1.0),
vec2f( 3.0, -1.0),
vec2f(-1.0, 3.0),
);
var out: VertexOutput;
out.position = vec4f(positions[idx], 0.0, 1.0);
out.uv = (positions[idx] + vec2f(1.0)) * 0.5;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
// TODO: replace with your shader logic
return vec4f(in.uv, 0.5, 1.0);
}
Vertex Buffer with DataView
GPU structs must be manually serialized. Vertex layout: [x, y, time, res.x, res.y, mouse.x, mouse.y] (all f32).
const vertexData = new ArrayBuffer(3 * 7 * 4);
const dv = new DataView(vertexData);
const verts = [
[-1, -1], [3, -1], [-1, 3],
];
verts.forEach(([x, y], i) => {
const offset = i * 7 * 4;
dv.setFloat32(offset + 0, x, true);
dv.setFloat32(offset + 4, y, true);
dv.setFloat32(offset + 8, time, true);
dv.setFloat32(offset + 12, width, true);
dv.setFloat32(offset + 16, height, true);
dv.setFloat32(offset + 20, mouseX, true);
dv.setFloat32(offset + 24, mouseY, true);
});
Common Mistakes
- No KEEPALIVE → segfault or silent corruption mid-render.
bundleWGPU: false → runtime error; Electrobun won't include the WGPU native library.
- Not recreating swap chain on resize → distorted rendering after window resize.
- Forgetting
little-endian: true in DataView.setFloat32 → garbage GPU data on most platforms.
- Blocking the render loop → use async I/O outside the render interval, never inside.