The webgpu npm package segfaults Bun. This is a WebGPU that runs there. A bun:ffi binding to
both major implementations — wgpu, the core of WebGPU in
Firefox, Servo and Deno, and Chromium's Dawn —
selectable at runtime, API-compatible with webgpu.
Headless compute, offscreen rendering and shader validation on Windows x64, Linux x64/arm64 and
macOS arm64.
bun add wgpu-bunimport { create, globals } from 'wgpu-bun';
Object.assign(globalThis, globals); // GPUBufferUsage, GPUShaderStage, …
const gpu = create([]);
const adapter = await gpu.requestAdapter();
const device = await adapter!.requestDevice();
device.pushErrorScope('validation');
const module = device.createShaderModule({ code: '/* wgsl */' });
const error = await device.popErrorScope(); // ← reports, with negative tests proving it can
if (error) throw new Error(error.message);The native library and the ABI shim arrive with it as an optionalDependencies platform package
matching your os/cpu. No install hook, no toolchain, no cargo.
That is examples/sky.ts, a WGSL port of RedPewEngine's
Hillaire atmosphere: two compute kernels build the transmittance and multiple-scattering tables, a
third ray-marches the sky into a 192×108 image, a fullscreen shader adds an analytic sun disc and
exposes it.
bun run examples/sky.ts # → sky.pngPlus triangle.ts, the shortest thing that proves an install works. Gallery:
examples/README.md.
import { saveTexturePng, readTexture, encodePng } from 'wgpu-bun/image';
await saveTexturePng(device, target, 'frame.png');copyTextureToBuffer demands a 256-byte-aligned row stride, so the buffer that comes back is almost
never the image: a 1400-pixel-wide RGBA frame arrives with 5 600 bytes of pixels and 32 bytes of
padding per row, and code that ignores it renders a picture that shears. readTexture hands back
tightly packed rows, and refuses a texture without COPY_SRC up front because letting that reach
wgpuQueueSubmit aborts the process. encodePng has no dependencies. All of it is a subpath, so
create, globals and isMac stay the whole compatibility surface.
| wgpu-bun | bun-webgpu |
webgpu |
|
|---|---|---|---|
| Backend | wgpu-native — the Rust wgpu behind Firefox, Servo and Deno |
Dawn — Chromium's | Dawn — Chromium's |
| Runs under Bun | yes | yes | no — the N-API addon segfaults the runtime |
popErrorScope() |
reports, with negative tests proving it can go red | crashes its allocator, even on an empty scope | works |
getCompilationInfo() |
real diagnostics, synthesised from the validation error | unimplemented | works |
| Surfaces / windowing | no — headless and offscreen only | no | no |
bun-webgpu covers the heavy end of the API competently and is a reasonable answer if Dawn is what
you want. Full assessment: docs/ERROR-PATH.md.
The default is wgpu-native; WGPU_BUN_IMPL=dawn selects Dawn at runtime, from a separate opt-in
platform package — name-reserved today, so build it from a checkout until the first Dawn release.
The 92 aggregates this binding lays out are field-identical in both headers, so nothing above the
loading layer changes, and the whole suite runs green against either, on all three platforms in
public CI, over Vulkan, D3D12 and Metal. Google ships static archives only, so this repository
links its own, with the ABI shim fused in, from a tag and a sha256. What differs:
docs/DAWN.md.
Green on every supported platform, against three graphics APIs, on wgpu-native v29.0.1.1:
| platform | adapter | API |
|---|---|---|
win32-x64 |
Microsoft Basic Render Driver (WARP), and a discrete NVIDIA adapter locally | D3D12 |
linux-x64 |
llvmpipe (Mesa lavapipe) | Vulkan |
linux-arm64 |
llvmpipe (Mesa lavapipe) | Vulkan |
darwin-arm64 |
Apple Paravirtual device | Metal |
Three calling conventions. CI legs that cannot reach a device fail rather than skip, so a green matrix means the suite ran, not that it was excused.
Implemented: adapter, device, buffers, textures, samplers, bind groups, pipelines, encoders,
queues; WGSL compilation, compute dispatch, render to texture, buffer readback, error scopes,
getCompilationInfo(), backend selection.
Refused, not stubbed. A call either does the thing or throws saying it does not exist, including the 40 wgpu-native symbols that abort the process, which are blocklisted by name. Out of scope: surfaces, render bundles, indirect draw, occlusion queries, external textures (why). Not done yet: no WebGPU CTS run, and no discrete GPU outside Windows (the full list).
wgpu-bun@29.x.y binds wgpu-native v29. That digit is not a maturity signal; it names the native
library inside, which decides ABI, validation strictness and WGSL acceptance. When upstream moves to
v30, so does this major; minor and patch are ordinary semver. A test asserts the major is
WGPU_NATIVE_MAJOR, so a pin bump cannot ship as 29.x and tell everyone the ABI did not move.
It ships v29 and accepts v27. If your Rust half is on wgpu 27, point WGPU_NATIVE_LIB at that
library, or run bun run fetch --generation 27; the same suite that certifies v29 runs against it on
every platform in CI. An untested generation is refused at load rather than warned about, because
the differences between generations produce wrong answers rather than errors. Measurements:
docs/GENERATIONS.md.
wgpu-native is pinned by URL and sha256 and fetched at release time: nothing committed, nothing
downloaded at install. Two upstreams are watched on different terms — wgpu-native by version, Dawn
by the calendar — and .github/workflows/upstream-watch.yml files an issue when one is due.
Details: docs/PACKAGING.md.
An import-specifier change and nothing else: the same three exports (create, globals, isMac)
over standard @webgpu/types. Route it through a
one-line local re-export module and swapping back is a single edit for a whole codebase. The
differences — the two Dawn-proprietary globals, navigator.gpu, copyExternalImageToTexture — are
in docs/COMPATIBILITY.md.
Confirming an install landed:
import { resolveNativeLibrary, STATUS } from 'wgpu-bun';
console.log(STATUS);
console.log(resolveNativeLibrary());
// { path: '…/vendor/win32-x64/lib/wgpu_native.dll', source: 'vendor',
// includeDir: '…/vendor/win32-x64/include', version: 'v29.0.1.1' }- docs/ABI.md — the by-value seam, the 40 symbols that abort, the layout oracle.
- docs/ERROR-PATH.md — error scopes, shader diagnostics, prior art.
- docs/COMPATIBILITY.md — the
webgpucontract, and what is out of scope. - docs/GENERATIONS.md — which wgpu-native generations load, and why.
- docs/DAWN.md — selecting Dawn, and every measured difference from wgpu-native.
- docs/PACKAGING.md — platform packages, no postinstall hook, pinning.
- docs/CI.md — GitHub Actions: which runner needs which driver, and no silent skips.
- docs/EVIDENCE.md — proven by execution versus argued from a spec; the gaps.
- docs/RELEASE.md — how a release is cut.
bun install
bun run fetch # download + verify the pinned wgpu-native for this host
bun run shim:build # build the ABI shim (needs cargo; see docs/ABI.md)
bun run check:layouts # confirm the generated struct layouts match those headers
bun run typecheck
bun test
bun run test:matrix # the suite × every implementation and backend this host can reachThe backend is a correctness knob — the same GPU exposes shader-f16 over Vulkan and not over
D3D12 — and CI cannot sweep it: every runner has exactly one usable backend. test:matrix does.
shim:build is optional only on win32-x64, where the direct path is correct anyway, and worth
building even there since it is the path that ships everywhere else. On the other three RIDs it is
not optional: without it the GPU suites skip with abi-unsupported.
MIT © 2026 Argon Inc — see LICENSE, covering this repository's own code only.
wgpu-native is dual-licensed MIT or Apache-2.0 by the
gfx-rs project. Its binaries are fetched from upstream's own releases rather than vendored here, but
the per-platform npm packages do redistribute them, so those declare MIT OR Apache-2.0 and carry
LICENSE-WGPU-NATIVE (why).
bun-webgpu (Apache-2.0) is credited as prior art; not a
dependency, and no code is taken from it.




