concept-collection / turing-surface-cache
turing-surface-cache / src / cli / webgpu.ts
78 lines · 3.4 KBBlameHistoryRaw
1/**
2 * Desktop WebGPU, via the optional `webgpu` package (prebuilt Google Dawn).
3 *
4 * Dawn is installed under the globals the transform code expects
5 * (navigator.gpu, GPUBufferUsage, …), so everything under src/ runs here
6 * unchanged — including requestShtDevice(), which makes the same device
7 * request the browser makes. This file is the whole of what the command line
8 * has that the page does not.
9 *
10 * Adapted from turing-surface's scripts/nodeWebGpu.ts.
11 */
13export const errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e));
15/** Installs Dawn and returns a human-readable runtime description. */
16export async function installWebGpu(): Promise<string> {
17 // The import specifier is indirect so that a build (and a type-check)
18 // does not require the optional package.
19 const specifier = 'webgpu';
20 let mod: {
21 create: (flags: string[]) => GPU;
22 globals: Record<string, unknown>;
23 };
24 try {
25 mod = await import(/* @vite-ignore */ specifier);
26 } catch (e) {
27 // Distinguish "not installed" from "installed but the prebuilt Dawn binary
28 // will not load" — the second is what a machine missing a system library
29 // looks like, and reporting it as the first sends people in circles.
30 const detail = errMsg(e);
31 if (/Cannot find (package|module) '?webgpu'?/.test(detail)) {
32 throw new Error(
33 'desktop WebGPU needs the optional `webgpu` package (prebuilt Google Dawn),\n' +
34 'which npm may have skipped silently. `npm ls webgpu` says whether it is\n' +
35 'there; `npm install webgpu` installs it.',
36 );
37 }
38 const glibc = /GLIBC_([0-9.]+)/.exec(detail);
39 throw new Error(
40 `the \`webgpu\` package is installed but did not load:\n ${detail}\n` +
41 (glibc
42 ? `Dawn's prebuilt binary wants glibc ${glibc[1]} or newer and this host is older\n` +
43 '(`ldd --version` says how old). No flag bridges that — use a container with a\n' +
44 'newer base image, or a newer host.'
45 : 'That is usually the prebuilt Dawn binary missing a system library.'),
46 );
47 }
48 Object.assign(globalThis, mod.globals);
49 // DAWN_FLAGS is ';'-separated because individual Dawn options take
50 // comma-separated lists, e.g. 'enable-dawn-features=allow_unsafe_apis,...'
51 const dawnFlags = process.env.DAWN_FLAGS?.split(';').filter(Boolean) ?? [];
52 Object.defineProperty(globalThis, 'navigator', {
53 value: { gpu: mod.create(dawnFlags) },
54 configurable: true,
55 writable: true,
56 });
57 const { version } = await import(/* @vite-ignore */ `${specifier}/package.json`, {
58 with: { type: 'json' },
59 }).then(
60 (m) => m.default as { version: string },
61 () => ({ version: '?' }),
62 );
63 return `node-webgpu ${version} (Google Dawn)`;
66/** The hint to print when Dawn loads but finds no adapter. */
67export const NO_ADAPTER_HINT =
68 ' Dawn reaches the GPU through Vulkan on Linux and Windows, Metal on macOS,\n' +
69 " so a headless box may have no adapter at all. DAWN_FLAGS='backend=vulkan'\n" +
70 ' makes it explain itself.';
72/**
73 * Adapters that are not really GPUs. A run on one of these is perhaps a
74 * thousand times slower than on hardware, which is fast enough to look like it
75 * is working and slow enough to be worthless — so it is worth saying out loud.
76 */
77export const isSoftwareAdapter = (name: string): boolean =>
78 /swiftshader|llvmpipe|lavapipe|software|microsoft basic|warp/i.test(name);