/ concept-collection / fastandaccurate
Sign in
concept-collection / fastandaccurate
fastandaccurate / src / harness / webgpuDevice.ts
115 lines · 4.0 KBBlameHistoryRaw
1// Getting a WebGPU device, in the browser and outside it.
2//
3// In the browser navigator.gpu is there or it is not. In node it comes from
4// the optional `webgpu` package (prebuilt Google Dawn), imported through a
5// variable specifier so that neither the site bundle nor the command line
6// bundle tries to resolve a native module at build time. The package is an
7// optionalDependency and is 68 MB, so the command line does not ship it:
8// a run without it skips the WebGPU solvers the same way a run without
9// matlab on the PATH skips the MATLAB ones.
11export interface GpuEnvironment {
12 device: GPUDevice;
13 /** Adapter description for the result file's environment record. */
14 adapter: string;
15 /** How WebGPU was reached, for the same record. */
16 via: string;
19let cached: Promise<GpuEnvironment> | null = null;
21/** Whether this is node rather than a page or a worker, which decides
22 * whether a missing navigator.gpu means "install Dawn" or "this browser
23 * does not have WebGPU". */
24function isNode(): boolean {
25 return typeof process !== "undefined" && !!process.versions?.node;
28async function installNodeWebGpu(): Promise<string> {
29 const specifier = "webgpu";
30 let mod: { create: (flags: string[]) => GPU; globals: Record<string, unknown> };
31 try {
32 mod = (await import(/* @vite-ignore */ specifier)) as typeof mod;
33 } catch (e) {
34 const detail = e instanceof Error ? e.message : String(e);
35 if (/Cannot find (package|module) '?webgpu'?/.test(detail)) {
36 throw new Error(
37 "WebGPU outside the browser needs the optional `webgpu` package " +
38 "(prebuilt Google Dawn): npm install webgpu"
39 );
40 }
41 // Installed but unloadable is a different problem from missing, and
42 // reporting it as missing sends people in circles.
43 throw new Error(`the \`webgpu\` package is installed but did not load: ${detail}`);
44 }
45 Object.assign(globalThis, mod.globals);
46 Object.defineProperty(globalThis, "navigator", {
47 value: { gpu: mod.create([]) },
48 configurable: true,
49 writable: true,
50 });
51 return "node-webgpu (Google Dawn)";
54/** A device, requested once and shared. Throws with an actionable message
55 * when there is no WebGPU here. */
56export function requestGpu(): Promise<GpuEnvironment> {
57 cached ??= (async () => {
58 let via: string;
59 if (typeof navigator !== "undefined" && navigator.gpu) {
60 via = "browser";
61 } else if (isNode()) {
62 via = await installNodeWebGpu();
63 } else {
64 throw new Error(
65 "this browser has no WebGPU: navigator.gpu is absent. Chrome and " +
66 "Edge have it; Safari and Firefox need a recent version."
67 );
68 }
69 const gpu = (navigator as Navigator).gpu;
70 if (!gpu) throw new Error("no navigator.gpu after setup");
71 const adapter = await gpu.requestAdapter();
72 if (!adapter) {
73 throw new Error(
74 "WebGPU found no adapter. A headless machine often has none at all; " +
75 "in Chrome chrome://gpu says why."
76 );
77 }
78 const info = adapter.info as GPUAdapterInfo | undefined;
79 const parts = [info?.vendor, info?.architecture, info?.device]
80 .filter((s) => s)
81 .join(" ");
82 const device = await adapter.requestDevice();
83 // A device lost mid-sweep would otherwise show up as a wrong answer.
84 device.lost.then((reason) => {
85 console.error(`WebGPU device lost: ${reason.reason} ${reason.message}`);
86 });
87 return {
88 device,
89 adapter: (info?.description || parts || "unknown adapter").trim(),
90 via,
91 };
92 })();
93 return cached;
96/** Whether a WebGPU device can be had here. Used to skip the WebGPU
97 * solvers rather than fail a whole run. */
98export async function gpuAvailable(): Promise<boolean> {
99 try {
100 await requestGpu();
101 return true;
102 } catch {
103 return false;
104 }
107/** Why WebGPU is unavailable, for a message to the user. */
108export async function gpuUnavailableReason(): Promise<string | null> {
109 try {
110 await requestGpu();
111 return null;
112 } catch (e) {
113 return e instanceof Error ? e.message : String(e);
114 }
moveopenescclose