@nadrif/thread 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gpu/env.js ADDED
@@ -0,0 +1,372 @@
1
+ /**
2
+ * @file GPU environment detection and platform-specific adapters.
3
+ *
4
+ * Detects WebGPU support across browser, Node.js, Deno, and Edge
5
+ * environments. Provides a unified interface for requesting GPU
6
+ * adapters and devices regardless of the runtime.
7
+ *
8
+ * **Supported environments:**
9
+ * - **Browser** — `navigator.gpu.requestAdapter()`
10
+ * - **Node.js** — `@aspect-build/webgpu-node`, `node-webgpu`, or GPUProcess
11
+ * - **Bun** — Bun's built-in WebGPU (experimental) or Node.js bindings
12
+ * - **Deno** — `Deno.gpu` (Deno's built-in WebGPU)
13
+ * - **Edge** — Browser-compatible WebGPU (Cloudflare, Vercel)
14
+ *
15
+ * @example
16
+ * ```js
17
+ * import { gpuEnv, requestGPUAdapter, isGPUAvailable } from './env.js';
18
+ *
19
+ * if (await isGPUAvailable()) {
20
+ * const adapter = await requestGPUAdapter({ powerPreference: 'high-performance' });
21
+ * const device = await adapter.requestDevice();
22
+ * // Use device for compute shaders...
23
+ * } else {
24
+ * console.warn('WebGPU not available — falling back to CPU');
25
+ * }
26
+ * ```
27
+ *
28
+ * @module gpu/env
29
+ */
30
+
31
+ import { env } from '../env.js';
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // GPU detection (cached)
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** @type {Promise<boolean>|null} Cached GPU availability check. */
38
+ let _gpuCheckPromise = null;
39
+
40
+ /**
41
+ * Check if WebGPU is available in the current environment.
42
+ *
43
+ * This performs async detection (some runtimes require `await` to
44
+ * determine GPU availability). The result is cached after the first call.
45
+ *
46
+ * @returns {Promise<boolean>} `true` if WebGPU is available and usable.
47
+ *
48
+ * @example
49
+ * ```js
50
+ * if (await isGPUAvailable()) {
51
+ * const gpu = new GPUCompute({ shader: myShader });
52
+ * await gpu.init();
53
+ * }
54
+ * ```
55
+ */
56
+ export async function isGPUAvailable() {
57
+ if (_gpuCheckPromise) return _gpuCheckPromise;
58
+ _gpuCheckPromise = _checkGPU();
59
+ return _gpuCheckPromise;
60
+ }
61
+
62
+ /**
63
+ * @private
64
+ */
65
+ async function _checkGPU() {
66
+ // --- Browser / Edge ---
67
+ if (typeof navigator !== 'undefined' && navigator.gpu) {
68
+ try {
69
+ const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
70
+ return !!adapter;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ // --- Deno ---
77
+ if (env.isDeno && typeof Deno !== 'undefined' && Deno.gpu) {
78
+ try {
79
+ const adapter = await Deno.gpu.requestAdapter({ powerPreference: 'high-performance' });
80
+ return !!adapter;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ // --- Node.js / Bun — try dynamic imports of WebGPU bindings ---
87
+ if (env.isNode || env.isBun) {
88
+ return await _checkNodeGPU();
89
+ }
90
+
91
+ return false;
92
+ }
93
+
94
+ /**
95
+ * @private
96
+ */
97
+ async function _checkNodeGPU() {
98
+ // Try various Node.js WebGPU implementations
99
+ const candidates = [
100
+ '@aspect-build/webgpu-node',
101
+ 'node-webgpu',
102
+ '@aspect-build/webgpu',
103
+ ];
104
+
105
+ for (const pkg of candidates) {
106
+ try {
107
+ const mod = await import(pkg);
108
+ if (mod && (mod.gpu || mod.navigator?.gpu || mod.default?.gpu)) {
109
+ return true;
110
+ }
111
+ } catch {
112
+ // Not installed — try next
113
+ }
114
+ }
115
+
116
+ // Try Bun's built-in (if available in the future)
117
+ if (env.isBun) {
118
+ try {
119
+ if (typeof globalThis.Bun?.gpu !== 'undefined') return true;
120
+ } catch { /* ignore */ }
121
+
122
+ // Try bun-webgpu (Dawn FFI bindings for Bun)
123
+ try {
124
+ const mod = await import('bun-webgpu');
125
+ if (mod && typeof mod.setupGlobals === 'function') {
126
+ mod.setupGlobals();
127
+ return typeof navigator !== 'undefined' && !!navigator.gpu;
128
+ }
129
+ } catch { /* not installed */ }
130
+ }
131
+
132
+ return false;
133
+ }
134
+
135
+ /**
136
+ * Synchronous GPU availability check.
137
+ *
138
+ * Returns `true` if the `navigator.gpu` API is present in the global
139
+ * scope, or if `Bun.gpu` is available (Bun's experimental built-in).
140
+ * This is a fast, synchronous check — it does NOT verify that a GPU
141
+ * adapter can actually be obtained. Use {@link isGPUAvailable} for
142
+ * a reliable async check.
143
+ *
144
+ * @returns {boolean} `true` if `navigator.gpu` or `Bun.gpu` exists.
145
+ */
146
+ export function isGPUSync() {
147
+ if (typeof navigator !== 'undefined' && navigator.gpu) return true;
148
+ if (env.isDeno && typeof Deno !== 'undefined' && Deno.gpu) return true;
149
+ if (env.isBun && typeof globalThis.Bun !== 'undefined' && globalThis.Bun.gpu) return true;
150
+ return false;
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // GPU adapter request
155
+ // ---------------------------------------------------------------------------
156
+
157
+ /**
158
+ * Request a GPU adapter from the current environment.
159
+ *
160
+ * Abstracts the differences between browser `navigator.gpu`,
161
+ * Deno `Deno.gpu`, and Node.js WebGPU bindings.
162
+ *
163
+ * @param {Object} [options={}]
164
+ * @param {'low-power'|'high-performance'|undefined} [options.powerPreference]
165
+ * @param {Object} [options.forceFallbackAdapter] - Force software renderer.
166
+ * @returns {Promise<GPUAdapter|null>} The requested adapter, or `null`.
167
+ *
168
+ * @example
169
+ * ```js
170
+ * const adapter = await requestGPUAdapter({
171
+ * powerPreference: 'high-performance',
172
+ * });
173
+ *
174
+ * if (adapter) {
175
+ * const device = await adapter.requestDevice();
176
+ * // Ready for compute shaders
177
+ * }
178
+ * ```
179
+ */
180
+ export async function requestGPUAdapter(options = {}) {
181
+ const { powerPreference = 'high-performance', ...rest } = options;
182
+
183
+ // --- Browser ---
184
+ if (typeof navigator !== 'undefined' && navigator.gpu) {
185
+ try {
186
+ return await navigator.gpu.requestAdapter({ powerPreference, ...rest });
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+
192
+ // --- Deno ---
193
+ if (env.isDeno && typeof Deno !== 'undefined' && Deno.gpu) {
194
+ try {
195
+ return await Deno.gpu.requestAdapter({ powerPreference, ...rest });
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ // --- Node.js ---
202
+ if (env.isNode || env.isBun) {
203
+ return await _requestNodeGPUAdapter(options);
204
+ }
205
+
206
+ return null;
207
+ }
208
+
209
+ /**
210
+ * @private
211
+ */
212
+ async function _requestNodeGPUAdapter(options) {
213
+ const candidates = [
214
+ 'bun-webgpu',
215
+ '@aspect-build/webgpu-node',
216
+ 'node-webgpu',
217
+ '@aspect-build/webgpu',
218
+ 'webgpu',
219
+ ];
220
+
221
+ for (const pkg of candidates) {
222
+ try {
223
+ const mod = await import(pkg);
224
+ // bun-webgpu requires setupGlobals() to set navigator.gpu
225
+ if (pkg === 'bun-webgpu' && typeof mod.setupGlobals === 'function') {
226
+ mod.setupGlobals();
227
+ }
228
+ const gpu = mod.gpu || mod.navigator?.gpu || mod.default?.gpu || (typeof navigator !== 'undefined' && navigator.gpu);
229
+ if (gpu && typeof gpu.requestAdapter === 'function') {
230
+ return await gpu.requestAdapter(options);
231
+ }
232
+ } catch {
233
+ // Not available
234
+ }
235
+ }
236
+
237
+ return null;
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // GPU info / diagnostics
242
+ // ---------------------------------------------------------------------------
243
+
244
+ /**
245
+ * Get a diagnostic snapshot of GPU support in the current environment.
246
+ *
247
+ * Useful for debugging GPU availability issues.
248
+ *
249
+ * @returns {Promise<Object>} Diagnostic information.
250
+ *
251
+ * @example
252
+ * ```js
253
+ * const info = await gpuInfo();
254
+ * console.log(info);
255
+ * // {
256
+ * available: true,
257
+ * runtime: 'browser',
258
+ * adapterName: 'NVIDIA GeForce RTX 4090',
259
+ * features: ['timestamp-query', 'shader-f16'],
260
+ * limits: { maxBufferSize: 2147483648, ... },
261
+ * fallback: false
262
+ * }
263
+ * ```
264
+ */
265
+ export async function gpuInfo() {
266
+ const available = await isGPUAvailable();
267
+ const info = {
268
+ available,
269
+ runtime: env.runtime,
270
+ adapterName: null,
271
+ features: [],
272
+ limits: {},
273
+ fallback: false,
274
+ };
275
+
276
+ if (!available) return info;
277
+
278
+ try {
279
+ const adapter = await requestGPUAdapter({ powerPreference: 'low-power' });
280
+ if (adapter) {
281
+ info.adapterName = adapter.name || 'unknown';
282
+ info.features = [...(adapter.features || [])];
283
+ info.limits = adapter.limits || {};
284
+ }
285
+ } catch { /* ignore */ }
286
+
287
+ return info;
288
+ }
289
+
290
+ /**
291
+ * Request a GPU device with sensible defaults.
292
+ *
293
+ * Convenience wrapper around {@link requestGPUAdapter} that also
294
+ * requests a device. Returns both the adapter and device.
295
+ *
296
+ * @param {Object} [options={}]
297
+ * @param {'low-power'|'high-performance'} [options.powerPreference]
298
+ * @param {Object} [options.requiredLimits] - Minimum device limits.
299
+ * @returns {Promise<{ adapter: GPUAdapter, device: GPUDevice }|null>}
300
+ * Adapter and device, or `null` if unavailable.
301
+ *
302
+ * @example
303
+ * ```js
304
+ * const result = await requestGPUDevice({ powerPreference: 'high-performance' });
305
+ * if (result) {
306
+ * const { device } = result;
307
+ * const buffer = device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE });
308
+ * }
309
+ * ```
310
+ */
311
+ export async function requestGPUDevice(options = {}) {
312
+ const { requiredLimits, ...adapterOptions } = options;
313
+ const adapter = await requestGPUAdapter(adapterOptions);
314
+ if (!adapter) return null;
315
+
316
+ try {
317
+ const device = await adapter.requestDevice({
318
+ requiredLimits: requiredLimits || {},
319
+ });
320
+
321
+ // Handle device loss
322
+ device.lost.then((info) => {
323
+ // Device lost — caller should handle re-init
324
+ });
325
+
326
+ return { adapter, device };
327
+ } catch {
328
+ return null;
329
+ }
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // GPU environment object (unified interface)
334
+ // ---------------------------------------------------------------------------
335
+
336
+ /**
337
+ * Comprehensive GPU environment information.
338
+ *
339
+ * @type {{
340
+ * available: boolean,
341
+ * sync: boolean,
342
+ * runtime: string,
343
+ * isBrowser: boolean,
344
+ * isNode: boolean,
345
+ * isDeno: boolean,
346
+ * requestAdapter: typeof requestGPUAdapter,
347
+ * requestDevice: typeof requestGPUDevice,
348
+ * info: typeof gpuInfo,
349
+ * }}
350
+ */
351
+ export const gpuEnv = Object.freeze({
352
+ /** @type {boolean} Async GPU availability check (cached). */
353
+ get available() { return isGPUAvailable(); },
354
+ /** @type {boolean} Sync check for `navigator.gpu`. */
355
+ get sync() { return isGPUSync(); },
356
+ /** @type {string} Current runtime identifier. */
357
+ runtime: env.runtime,
358
+ /** @type {boolean} `true` if in a browser environment. */
359
+ isBrowser: env.isBrowser,
360
+ /** @type {boolean} `true` if in Node.js or Bun. */
361
+ isNode: env.isNode || env.isBun,
362
+ /** @type {boolean} `true` if in Deno. */
363
+ isDeno: env.isDeno,
364
+ /** Request a GPU adapter. */
365
+ requestAdapter: requestGPUAdapter,
366
+ /** Request a GPU device. */
367
+ requestDevice: requestGPUDevice,
368
+ /** Get diagnostic info. */
369
+ info: gpuInfo,
370
+ });
371
+
372
+ export default gpuEnv;