gpu-atlas 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.
@@ -0,0 +1,1938 @@
1
+ const Et = 4, Tt = 2;
2
+ class D extends Error {
3
+ constructor(t) {
4
+ super(t), this.name = "WebGPUUnavailable";
5
+ }
6
+ }
7
+ async function ke(e) {
8
+ if (typeof navigator > "u" || !navigator.gpu)
9
+ throw new D("navigator.gpu is missing — this browser has no WebGPU");
10
+ const t = await navigator.gpu.requestAdapter(
11
+ e ? { powerPreference: e } : void 0
12
+ );
13
+ if (!t)
14
+ throw new D(
15
+ "requestAdapter returned null — the WebGPU API exists but no usable adapter does"
16
+ );
17
+ const r = await Se(t, e), n = [...t.features], i = Me(t.limits), a = [];
18
+ let s = await N(t, n, i);
19
+ if (s || (a.push("requiredLimits (all)"), s = await N(t, n, void 0)), !s) {
20
+ a.push("requiredFeatures (all)");
21
+ const u = [];
22
+ for (const c of n) {
23
+ const l = await N(t, [...u, c], void 0);
24
+ l ? (u.push(c), l.destroy()) : a.push(`feature:${c}`);
25
+ }
26
+ s = await N(t, u, void 0);
27
+ }
28
+ if (!s)
29
+ throw new D(
30
+ "requestDevice kept failing — there is an adapter but no device can be created from it"
31
+ );
32
+ const o = {
33
+ features: [...t.features].sort(),
34
+ limits: i,
35
+ preferredCanvasFormat: Ee()
36
+ };
37
+ return { adapter: t, device: s, identity: r, declared: o, denied: a, lost: s.lost };
38
+ }
39
+ async function N(e, t, r) {
40
+ try {
41
+ const n = { requiredFeatures: t };
42
+ return r && (n.requiredLimits = r), await e.requestDevice(n);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ async function Se(e, t) {
48
+ let r = e.info;
49
+ if (!r) {
50
+ const n = e;
51
+ if (typeof n.requestAdapterInfo == "function")
52
+ try {
53
+ r = await n.requestAdapterInfo();
54
+ } catch {
55
+ r = void 0;
56
+ }
57
+ }
58
+ return {
59
+ vendor: r?.vendor ?? "",
60
+ architecture: r?.architecture ?? "",
61
+ device: r?.device ?? "",
62
+ description: r?.description ?? "",
63
+ // isFallbackAdapter has lived on both the adapter and its info over time.
64
+ isFallbackAdapter: e.isFallbackAdapter ?? r?.isFallbackAdapter ?? !1,
65
+ powerPreference: t ?? "default"
66
+ };
67
+ }
68
+ function Me(e) {
69
+ const t = {};
70
+ for (const r of Pe(e)) {
71
+ const n = e[r];
72
+ typeof n == "number" && Number.isFinite(n) && (t[r] = n);
73
+ }
74
+ return t;
75
+ }
76
+ function Pe(e) {
77
+ const t = /* @__PURE__ */ new Set();
78
+ let r = Object.getPrototypeOf(e);
79
+ for (; r && r !== Object.prototype; ) {
80
+ for (const n of Object.getOwnPropertyNames(r))
81
+ n !== "constructor" && t.add(n);
82
+ r = Object.getPrototypeOf(r);
83
+ }
84
+ for (const n of Object.keys(e)) t.add(n);
85
+ return [...t].sort();
86
+ }
87
+ function Ee() {
88
+ try {
89
+ return navigator.gpu.getPreferredCanvasFormat();
90
+ } catch {
91
+ return "";
92
+ }
93
+ }
94
+ async function Te() {
95
+ const e = typeof navigator < "u" ? navigator.userAgent : "", t = navigator.userAgentData;
96
+ let r, n, i, a;
97
+ if (t) {
98
+ n = t.mobile, r = t.platform;
99
+ try {
100
+ const o = await t.getHighEntropyValues(["platformVersion", "fullVersionList"]), c = (o.fullVersionList ?? t.brands)?.find((l) => !/Not.?A.?Brand/i.test(l.brand));
101
+ c && (i = c.brand, a = c.version), o.platformVersion && (r = `${r} ${o.platformVersion}`);
102
+ } catch {
103
+ }
104
+ }
105
+ const s = Ae(e);
106
+ return {
107
+ // The UA string is parsed above but deliberately not kept: everything the
108
+ // profile needs from it is already broken out, and the raw value only adds
109
+ // identifying detail to something people are asked to share.
110
+ platform: r,
111
+ browser: i ?? s.browser,
112
+ browserVersion: a ?? s.version,
113
+ mobile: n ?? /Mobi|Android|iPhone|iPad/i.test(e),
114
+ deviceMemoryGB: navigator.deviceMemory,
115
+ hardwareConcurrency: navigator.hardwareConcurrency,
116
+ devicePixelRatio: typeof devicePixelRatio == "number" ? devicePixelRatio : 1
117
+ };
118
+ }
119
+ function Ae(e) {
120
+ const t = [
121
+ ["Edge", /Edg(?:e|A|iOS)?\/([\d.]+)/],
122
+ ["Opera", /OPR\/([\d.]+)/],
123
+ ["Firefox", /Firefox\/([\d.]+)/],
124
+ ["Chrome", /(?:Chrome|CriOS)\/([\d.]+)/],
125
+ ["Safari", /Version\/([\d.]+).*Safari/]
126
+ ];
127
+ for (const [r, n] of t) {
128
+ const i = e.match(n);
129
+ if (i) return { browser: r, version: i[1] };
130
+ }
131
+ return { browser: "unknown", version: "" };
132
+ }
133
+ const d = (e, t, r, n, i) => ({ format: e, kind: "color", texel: t, filterable: r, expect: n, requiresFeature: i }), I = { renderable: !1, blendable: !1, storage: !1 }, A = { renderable: !0, blendable: !0, storage: !1 }, y = { renderable: !0, blendable: !1, storage: !1 }, ae = { renderable: !0, blendable: !0, storage: !0 }, x = { renderable: !0, blendable: !1, storage: !0 }, Be = { renderable: !1, blendable: !1, storage: !0 }, ie = [
134
+ // ── 8-bit ──
135
+ d("r8unorm", "f32", !0, A),
136
+ d("r8snorm", "f32", !0, I),
137
+ d("r8uint", "u32", !1, y),
138
+ d("r8sint", "i32", !1, y),
139
+ // ── 16-bit ──
140
+ d("r16uint", "u32", !1, y),
141
+ d("r16sint", "i32", !1, y),
142
+ d("r16float", "f32", !0, A),
143
+ d("rg8unorm", "f32", !0, A),
144
+ d("rg8snorm", "f32", !0, I),
145
+ d("rg8uint", "u32", !1, y),
146
+ d("rg8sint", "i32", !1, y),
147
+ // ── 32-bit ──
148
+ d("r32uint", "u32", !1, x),
149
+ d("r32sint", "i32", !1, x),
150
+ // r32float renders but does not blend without float32-blendable
151
+ d("r32float", "f32", !1, x),
152
+ d("rg16uint", "u32", !1, y),
153
+ d("rg16sint", "i32", !1, y),
154
+ d("rg16float", "f32", !0, A),
155
+ d("rgba8unorm", "f32", !0, ae),
156
+ d("rgba8unorm-srgb", "f32", !0, A),
157
+ d("rgba8snorm", "f32", !0, Be),
158
+ d("rgba8uint", "u32", !1, x),
159
+ d("rgba8sint", "i32", !1, x),
160
+ d("bgra8unorm", "f32", !0, A),
161
+ d("bgra8unorm-srgb", "f32", !0, A),
162
+ d("rgb10a2uint", "u32", !1, y),
163
+ d("rgb10a2unorm", "f32", !0, A),
164
+ // rg11b10ufloat rendering sits behind its own feature gate
165
+ d("rg11b10ufloat", "f32", !0, I),
166
+ // ── 64-bit ──
167
+ d("rg32uint", "u32", !1, x),
168
+ d("rg32sint", "i32", !1, x),
169
+ d("rg32float", "f32", !1, x),
170
+ d("rgba16uint", "u32", !1, x),
171
+ d("rgba16sint", "i32", !1, x),
172
+ d("rgba16float", "f32", !0, ae),
173
+ // ── 128-bit ──
174
+ d("rgba32uint", "u32", !1, x),
175
+ d("rgba32sint", "i32", !1, x),
176
+ d("rgba32float", "f32", !1, x),
177
+ // ── Depth / stencil ──
178
+ {
179
+ format: "stencil8",
180
+ kind: "depth",
181
+ texel: "u32",
182
+ filterable: !1,
183
+ hasStencil: !0,
184
+ expect: y
185
+ },
186
+ {
187
+ format: "depth16unorm",
188
+ kind: "depth",
189
+ texel: "f32",
190
+ filterable: !1,
191
+ hasDepth: !0,
192
+ expect: y
193
+ },
194
+ {
195
+ format: "depth24plus",
196
+ kind: "depth",
197
+ texel: "f32",
198
+ filterable: !1,
199
+ hasDepth: !0,
200
+ expect: y
201
+ },
202
+ {
203
+ format: "depth24plus-stencil8",
204
+ kind: "depth",
205
+ texel: "f32",
206
+ filterable: !1,
207
+ hasDepth: !0,
208
+ hasStencil: !0,
209
+ expect: y
210
+ },
211
+ {
212
+ format: "depth32float",
213
+ kind: "depth",
214
+ texel: "f32",
215
+ filterable: !1,
216
+ hasDepth: !0,
217
+ expect: y
218
+ },
219
+ {
220
+ format: "depth32float-stencil8",
221
+ kind: "depth",
222
+ texel: "f32",
223
+ filterable: !1,
224
+ hasDepth: !0,
225
+ hasStencil: !0,
226
+ requiresFeature: "depth32float-stencil8",
227
+ expect: y
228
+ },
229
+ // ── Compressed: BC (desktop) ──
230
+ ...L([
231
+ "bc1-rgba-unorm",
232
+ "bc3-rgba-unorm",
233
+ "bc4-r-unorm",
234
+ "bc5-rg-unorm",
235
+ "bc6h-rgb-ufloat",
236
+ "bc7-rgba-unorm"
237
+ ], "texture-compression-bc", [4, 4]),
238
+ // ── Compressed: ETC2 (mobile) ──
239
+ ...L(
240
+ ["etc2-rgb8unorm", "etc2-rgba8unorm", "eac-r11unorm"],
241
+ "texture-compression-etc2",
242
+ [4, 4]
243
+ ),
244
+ // ── Compressed: ASTC (mobile) ──
245
+ ...L(["astc-4x4-unorm"], "texture-compression-astc", [4, 4]),
246
+ ...L(["astc-8x8-unorm"], "texture-compression-astc", [8, 8])
247
+ ];
248
+ function L(e, t, r) {
249
+ return e.map((n) => ({
250
+ format: n,
251
+ kind: "compressed",
252
+ texel: "f32",
253
+ filterable: !0,
254
+ requiresFeature: t,
255
+ block: r,
256
+ expect: I
257
+ }));
258
+ }
259
+ function $e(e) {
260
+ return ie.find((t) => t.format === e);
261
+ }
262
+ const se = /* @__PURE__ */ new Set([
263
+ "r8unorm",
264
+ "r8snorm",
265
+ "r8uint",
266
+ "r8sint",
267
+ "rg8unorm",
268
+ "rg8snorm",
269
+ "rg8uint",
270
+ "rg8sint",
271
+ "r16uint",
272
+ "r16sint",
273
+ "r16float",
274
+ "rg16uint",
275
+ "rg16sint",
276
+ "rg16float",
277
+ "rgb10a2unorm",
278
+ "rgb10a2uint",
279
+ "rg11b10ufloat"
280
+ ]), Re = /* @__PURE__ */ new Set(["r32float", "rg32float", "rgba32float"]);
281
+ function _e(e, t) {
282
+ const r = { ...e.expect };
283
+ return t.has("texture-formats-tier1") && se.has(e.format) && (r.storage = !0), t.has("rg11b10ufloat-renderable") && e.format === "rg11b10ufloat" && (r.renderable = !0, r.blendable = !0), t.has("bgra8unorm-storage") && e.format === "bgra8unorm" && (r.storage = !0), t.has("float32-blendable") && Re.has(e.format) && (r.blendable = !0), t.has("texture-formats-tier2") && e.kind === "color" && (r.storage = r.storage || se.has(e.format)), r;
284
+ }
285
+ function Ge(e) {
286
+ return e.has("texture-formats-tier1") || e.has("texture-formats-tier2");
287
+ }
288
+ function Fe(e) {
289
+ return e.kind === "depth" ? e.hasDepth ? "depth" : "uint" : e.texel === "u32" ? "uint" : e.texel === "i32" ? "sint" : e.filterable ? "float" : "unfilterable-float";
290
+ }
291
+ function Ue(e) {
292
+ return e.kind === "depth" && e.hasDepth ? "texture_depth_2d" : `texture_2d<${e.texel}>`;
293
+ }
294
+ const Y = ["validation", "out-of-memory", "internal"];
295
+ async function W(e, t, r = !1) {
296
+ for (const a of Y) e.pushErrorScope(a);
297
+ let n = null;
298
+ const i = [];
299
+ try {
300
+ n = await t();
301
+ } catch (a) {
302
+ i.push({ kind: "exception", message: X(a) });
303
+ }
304
+ if (r && i.length === 0)
305
+ try {
306
+ await e.queue.onSubmittedWorkDone();
307
+ } catch (a) {
308
+ i.push({ kind: "queue", message: X(a) });
309
+ }
310
+ for (let a = Y.length - 1; a >= 0; a--)
311
+ try {
312
+ const s = await e.popErrorScope();
313
+ s && i.push({ kind: Y[a], message: s.message });
314
+ } catch (s) {
315
+ i.push({ kind: "scope-unavailable", message: X(s) });
316
+ }
317
+ return { value: n, errors: i, ok: i.length === 0 && n !== null };
318
+ }
319
+ async function M(e, t, r = !1) {
320
+ const n = await W(e, async () => {
321
+ const i = await t();
322
+ return i === void 0 ? !0 : i;
323
+ }, r);
324
+ return { ok: n.ok, errors: n.errors };
325
+ }
326
+ function _(e, t) {
327
+ return t.map((r) => ({ ...r, stage: e }));
328
+ }
329
+ function O(e) {
330
+ return e[0]?.message ?? "no error reported";
331
+ }
332
+ function X(e) {
333
+ return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
334
+ }
335
+ function v(...e) {
336
+ for (const t of e)
337
+ try {
338
+ t?.destroy?.();
339
+ } catch {
340
+ }
341
+ }
342
+ const H = `
343
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
344
+ var p = array<vec2f, 3>(vec2f(-1., -1.), vec2f(3., -1.), vec2f(-1., 3.));
345
+ return vec4f(p[i], 0., 1.);
346
+ }`;
347
+ async function Ce(e, t, r, n) {
348
+ const i = r ? ie.filter((u) => r.includes(u.format)) : ie, a = e.createTexture({
349
+ size: [4, 4],
350
+ format: "rgba8unorm",
351
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
352
+ }), s = { view: a.createView() }, o = [];
353
+ for (let u = 0; u < i.length; u++)
354
+ o.push(await Oe(e, i[u], t, s)), n?.((u + 1) / i.length);
355
+ return v(a), o;
356
+ }
357
+ async function Oe(e, t, r, n) {
358
+ const i = {
359
+ format: t.format,
360
+ requiresFeature: t.requiresFeature,
361
+ featureDeclared: t.requiresFeature ? r.has(t.requiresFeature) : !0,
362
+ creatable: !1,
363
+ sampleable: !1,
364
+ renderable: !1,
365
+ blendable: !1,
366
+ storageWritable: !1,
367
+ multisample4x: !1,
368
+ errors: []
369
+ }, [a, s] = t.block ?? [4, 4], o = await W(
370
+ e,
371
+ () => e.createTexture({
372
+ size: [a, s],
373
+ format: t.format,
374
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
375
+ })
376
+ );
377
+ if (i.creatable = o.ok, !o.ok)
378
+ return i.errors.push(..._("create", o.errors)), i;
379
+ const u = o.value, c = await qe(e, t, u, n);
380
+ if (i.sampleable = c.ok, c.ok || i.errors.push(..._("sample", c.errors)), v(u), t.kind !== "compressed") if (t.kind === "depth") {
381
+ const l = await Ne(e, t);
382
+ i.renderable = l.ok, l.ok || i.errors.push(..._("render", l.errors));
383
+ } else {
384
+ const l = await ue(e, t, !1);
385
+ if (i.renderable = l.ok, l.ok || i.errors.push(..._("render", l.errors)), i.renderable) {
386
+ const f = await ue(e, t, !0);
387
+ i.blendable = f.ok, f.ok || i.errors.push(..._("blend", f.errors));
388
+ }
389
+ }
390
+ if (t.kind === "color") {
391
+ const l = await Le(e, t);
392
+ i.storageWritable = l.ok, l.ok || i.errors.push(..._("storage", l.errors));
393
+ }
394
+ if (i.renderable) {
395
+ const l = await De(e, t);
396
+ i.multisample4x = l.ok, l.ok || i.errors.push(..._("msaa4x", l.errors));
397
+ }
398
+ return i;
399
+ }
400
+ async function qe(e, t, r, n) {
401
+ const i = t.kind === "compressed", a = Fe(t);
402
+ return M(e, async () => {
403
+ const s = [{
404
+ binding: 0,
405
+ visibility: GPUShaderStage.FRAGMENT,
406
+ texture: { sampleType: a, viewDimension: "2d" }
407
+ }];
408
+ i && s.push({
409
+ binding: 1,
410
+ visibility: GPUShaderStage.FRAGMENT,
411
+ sampler: { type: t.filterable ? "filtering" : "non-filtering" }
412
+ });
413
+ const o = e.createBindGroupLayout({ entries: s }), u = e.createShaderModule({
414
+ code: H + Ie(t, i)
415
+ }), c = e.createRenderPipeline({
416
+ layout: e.createPipelineLayout({ bindGroupLayouts: [o] }),
417
+ vertex: { module: u, entryPoint: "vs" },
418
+ fragment: { module: u, entryPoint: "fs", targets: [{ format: "rgba8unorm" }] }
419
+ }), l = {};
420
+ t.hasDepth && t.hasStencil && (l.aspect = "depth-only");
421
+ const f = [
422
+ { binding: 0, resource: r.createView(l) }
423
+ ];
424
+ let p;
425
+ i && (p = e.createSampler(
426
+ t.filterable ? { magFilter: "linear", minFilter: "linear" } : {}
427
+ ), f.push({ binding: 1, resource: p }));
428
+ const h = e.createBindGroup({ layout: o, entries: f }), w = e.createCommandEncoder(), g = w.beginRenderPass({
429
+ colorAttachments: [{
430
+ view: n.view,
431
+ loadOp: "clear",
432
+ storeOp: "store",
433
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
434
+ }]
435
+ });
436
+ return g.setPipeline(c), g.setBindGroup(0, h), g.draw(3), g.end(), e.queue.submit([w.finish()]), !0;
437
+ }, !0);
438
+ }
439
+ async function ue(e, t, r) {
440
+ let n = null;
441
+ const i = await M(e, async () => {
442
+ const a = e.createTexture({
443
+ size: [4, 4],
444
+ format: t.format,
445
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
446
+ }), s = { format: t.format };
447
+ r && (s.blend = {
448
+ color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
449
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
450
+ });
451
+ const o = e.createShaderModule({
452
+ code: H + ye(t)
453
+ }), u = e.createRenderPipeline({
454
+ layout: "auto",
455
+ vertex: { module: o, entryPoint: "vs" },
456
+ fragment: { module: o, entryPoint: "fs", targets: [s] }
457
+ }), c = e.createCommandEncoder(), l = c.beginRenderPass({
458
+ colorAttachments: [{
459
+ view: a.createView(),
460
+ loadOp: "clear",
461
+ storeOp: "store",
462
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
463
+ }]
464
+ });
465
+ return l.setPipeline(u), l.draw(3), l.end(), e.queue.submit([c.finish()]), n = a, !0;
466
+ }, !0);
467
+ return v(n), i;
468
+ }
469
+ async function Ne(e, t) {
470
+ let r = null;
471
+ const n = await M(e, async () => {
472
+ const i = e.createTexture({
473
+ size: [4, 4],
474
+ format: t.format,
475
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
476
+ }), a = {
477
+ format: t.format
478
+ };
479
+ t.hasDepth && (a.depthWriteEnabled = !0, a.depthCompare = "always");
480
+ const s = e.createShaderModule({ code: H }), o = e.createRenderPipeline({
481
+ layout: "auto",
482
+ vertex: { module: s, entryPoint: "vs" },
483
+ depthStencil: a
484
+ }), u = { view: i.createView() };
485
+ t.hasDepth && (u.depthClearValue = 1, u.depthLoadOp = "clear", u.depthStoreOp = "store"), t.hasStencil && (u.stencilClearValue = 0, u.stencilLoadOp = "clear", u.stencilStoreOp = "store");
486
+ const c = e.createCommandEncoder(), l = c.beginRenderPass({
487
+ colorAttachments: [],
488
+ depthStencilAttachment: u
489
+ });
490
+ return l.setPipeline(o), l.draw(3), l.end(), e.queue.submit([c.finish()]), r = i, !0;
491
+ }, !0);
492
+ return v(r), n;
493
+ }
494
+ async function Le(e, t) {
495
+ let r = null;
496
+ const n = await M(e, async () => {
497
+ const i = e.createTexture({
498
+ size: [4, 4],
499
+ format: t.format,
500
+ usage: GPUTextureUsage.STORAGE_BINDING
501
+ }), a = t.texel === "u32" ? "vec4u" : t.texel === "i32" ? "vec4i" : "vec4f", s = e.createShaderModule({
502
+ code: `
503
+ @group(0) @binding(0) var t: texture_storage_2d<${t.format}, write>;
504
+ @compute @workgroup_size(1) fn cs() {
505
+ textureStore(t, vec2i(0, 0), ${a}(${t.texel === "f32" ? "1., 0., 0., 1." : "1, 0, 0, 1"}));
506
+ }`
507
+ }), o = e.createComputePipeline({
508
+ layout: "auto",
509
+ compute: { module: s, entryPoint: "cs" }
510
+ }), u = e.createBindGroup({
511
+ layout: o.getBindGroupLayout(0),
512
+ entries: [{ binding: 0, resource: i.createView() }]
513
+ }), c = e.createCommandEncoder(), l = c.beginComputePass();
514
+ return l.setPipeline(o), l.setBindGroup(0, u), l.dispatchWorkgroups(1), l.end(), e.queue.submit([c.finish()]), r = i, !0;
515
+ }, !0);
516
+ return v(r), n;
517
+ }
518
+ async function De(e, t) {
519
+ let r = null;
520
+ const n = await M(e, async () => {
521
+ const i = e.createTexture({
522
+ size: [4, 4],
523
+ format: t.format,
524
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
525
+ sampleCount: 4
526
+ }), a = e.createShaderModule({
527
+ code: H + (t.kind === "depth" ? "" : ye(t))
528
+ }), s = {
529
+ layout: "auto",
530
+ vertex: { module: a, entryPoint: "vs" },
531
+ multisample: { count: 4 }
532
+ };
533
+ if (t.kind === "depth") {
534
+ const l = { format: t.format };
535
+ t.hasDepth && (l.depthWriteEnabled = !0, l.depthCompare = "always"), s.depthStencil = l;
536
+ } else
537
+ s.fragment = {
538
+ module: a,
539
+ entryPoint: "fs",
540
+ targets: [{ format: t.format }]
541
+ };
542
+ const o = e.createRenderPipeline(s), u = e.createCommandEncoder();
543
+ let c;
544
+ if (t.kind === "depth") {
545
+ const l = { view: i.createView() };
546
+ t.hasDepth && (l.depthClearValue = 1, l.depthLoadOp = "clear", l.depthStoreOp = "discard"), t.hasStencil && (l.stencilClearValue = 0, l.stencilLoadOp = "clear", l.stencilStoreOp = "discard"), c = u.beginRenderPass({ colorAttachments: [], depthStencilAttachment: l });
547
+ } else
548
+ c = u.beginRenderPass({
549
+ colorAttachments: [{
550
+ view: i.createView(),
551
+ loadOp: "clear",
552
+ storeOp: "discard",
553
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
554
+ }]
555
+ });
556
+ return c.setPipeline(o), c.draw(3), c.end(), e.queue.submit([u.finish()]), r = i, !0;
557
+ }, !0);
558
+ return v(r), n;
559
+ }
560
+ function Ie(e, t) {
561
+ const n = `@group(0) @binding(0) var t: ${Ue(e)};`, i = t ? "@group(0) @binding(1) var s: sampler;" : "";
562
+ let a;
563
+ return t ? a = "return textureSample(t, s, vec2f(0.5, 0.5));" : e.kind === "depth" && e.hasDepth ? a = `let v = textureLoad(t, vec2i(0, 0), 0);
564
+ return vec4f(v, 0., 0., 1.);` : e.texel === "f32" ? a = "return textureLoad(t, vec2i(0, 0), 0);" : a = `let v = textureLoad(t, vec2i(0, 0), 0);
565
+ return vec4f(f32(v.x), f32(v.y), f32(v.z), 1.);`, `
566
+ ${n}
567
+ ${i}
568
+ @fragment fn fs() -> @location(0) vec4f {
569
+ ${a}
570
+ }`;
571
+ }
572
+ function ye(e) {
573
+ return e.texel === "u32" ? `
574
+ @fragment fn fs() -> @location(0) vec4u {
575
+ return vec4u(1u, 0u, 0u, 1u);
576
+ }` : e.texel === "i32" ? `
577
+ @fragment fn fs() -> @location(0) vec4i {
578
+ return vec4i(1, 0, 0, 1);
579
+ }` : `
580
+ @fragment fn fs() -> @location(0) vec4f {
581
+ return vec4f(1., 0., 0., 1.);
582
+ }`;
583
+ }
584
+ const G = `
585
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
586
+ var p = array<vec2f, 3>(vec2f(-1., -1.), vec2f(3., -1.), vec2f(-1., 3.));
587
+ return vec4f(p[i], 0., 1.);
588
+ }`, Q = [
589
+ {
590
+ id: "baseline",
591
+ description: "The simplest possible render pipeline — a reference point for the rest",
592
+ pipeline: "render",
593
+ code: `${G}
594
+ @fragment fn fs() -> @location(0) vec4f { return vec4f(1.); }`
595
+ },
596
+ {
597
+ id: "uniform-dynamic-index",
598
+ description: "Dynamic indexing into a uniform array — backends add clamping code or slow down",
599
+ pipeline: "render",
600
+ code: `${G}
601
+ struct Data { items: array<vec4f, 64> };
602
+ @group(0) @binding(0) var<uniform> data: Data;
603
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
604
+ let i = u32(pos.x) % 64u;
605
+ return data.items[i];
606
+ }`
607
+ },
608
+ {
609
+ id: "nested-loop-break",
610
+ description: "Nested loops with conditional breaks — where control flow flattening diverges",
611
+ pipeline: "render",
612
+ code: `${G}
613
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
614
+ var acc = 0.;
615
+ for (var i = 0u; i < 8u; i++) {
616
+ for (var j = 0u; j < 8u; j++) {
617
+ if (f32(i * j) > pos.x) { break; }
618
+ acc += 0.01;
619
+ }
620
+ if (acc > 0.5) { break; }
621
+ }
622
+ return vec4f(acc, 0., 0., 1.);
623
+ }`
624
+ },
625
+ {
626
+ id: "function-pointers",
627
+ description: "Passing var<function> pointers — naga and tint handle this differently",
628
+ pipeline: "render",
629
+ code: `${G}
630
+ fn bump(p: ptr<function, vec3f>, amount: f32) {
631
+ (*p) = (*p) + vec3f(amount);
632
+ }
633
+ @fragment fn fs() -> @location(0) vec4f {
634
+ var v = vec3f(0.);
635
+ bump(&v, 0.25);
636
+ bump(&v, 0.25);
637
+ return vec4f(v, 1.);
638
+ }`
639
+ },
640
+ {
641
+ id: "workgroup-atomics",
642
+ description: "Workgroup memory with atomics and barriers",
643
+ pipeline: "compute",
644
+ code: `
645
+ var<workgroup> counter: atomic<u32>;
646
+ @group(0) @binding(0) var<storage, read_write> out: array<u32>;
647
+ @compute @workgroup_size(64) fn cs(@builtin(local_invocation_id) lid: vec3u) {
648
+ if (lid.x == 0u) { atomicStore(&counter, 0u); }
649
+ workgroupBarrier();
650
+ atomicAdd(&counter, 1u);
651
+ workgroupBarrier();
652
+ if (lid.x == 0u) { out[0] = atomicLoad(&counter); }
653
+ }`
654
+ },
655
+ {
656
+ id: "storage-runtime-array",
657
+ description: "Runtime-sized array with arrayLength — exercises binding metadata handling",
658
+ pipeline: "compute",
659
+ code: `
660
+ @group(0) @binding(0) var<storage, read_write> data: array<f32>;
661
+ @compute @workgroup_size(64) fn cs(@builtin(global_invocation_id) gid: vec3u) {
662
+ let n = arrayLength(&data);
663
+ if (gid.x < n) { data[gid.x] = f32(n); }
664
+ }`
665
+ },
666
+ {
667
+ id: "struct-alignment",
668
+ description: "Nested struct alignment and stride — a classic source of backend layout bugs",
669
+ pipeline: "compute",
670
+ code: `
671
+ struct Inner { a: vec3f, b: f32 };
672
+ struct Outer { m: mat4x4f, items: array<Inner, 4>, flag: u32 };
673
+ @group(0) @binding(0) var<storage, read_write> data: Outer;
674
+ @compute @workgroup_size(1) fn cs() {
675
+ data.items[0].b = data.m[0][0] + f32(data.flag);
676
+ }`
677
+ },
678
+ {
679
+ id: "override-constants",
680
+ description: "Override constants — pipeline-time specialization, unevenly supported",
681
+ pipeline: "compute",
682
+ code: `
683
+ override tileSize: u32 = 8u;
684
+ @group(0) @binding(0) var<storage, read_write> out: array<u32>;
685
+ @compute @workgroup_size(1) fn cs() { out[0] = tileSize; }`
686
+ },
687
+ {
688
+ id: "textureSampleLevel-uniform",
689
+ description: "Texture sampling under non-uniform control flow — uniformity analysis differs",
690
+ pipeline: "render",
691
+ code: `${G}
692
+ @group(0) @binding(0) var t: texture_2d<f32>;
693
+ @group(0) @binding(1) var s: sampler;
694
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
695
+ var c = vec4f(0.);
696
+ if (pos.x > 1.) {
697
+ c = textureSampleLevel(t, s, vec2f(0.5), 0.);
698
+ }
699
+ return c;
700
+ }`
701
+ },
702
+ {
703
+ id: "long-unrolled",
704
+ description: "A large shader — exists to measure compile time",
705
+ pipeline: "render",
706
+ code: `${G}
707
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
708
+ var acc = vec3f(0.);
709
+ var p = pos.xyz * 0.01;
710
+ ${Array.from({ length: 64 }, (e, t) => ` p = fract(p * 1.${t % 9 + 1} + vec3f(${(t * 0.37).toFixed(3)}));
711
+ acc += p * ${(0.01 + t * 1e-3).toFixed(4)};`).join(`
712
+ `)}
713
+ return vec4f(acc, 1.);
714
+ }`
715
+ },
716
+ {
717
+ id: "f16-arithmetic",
718
+ description: "f16 arithmetic via shader-f16 — central to mobile performance, unevenly available",
719
+ requiresFeature: "shader-f16",
720
+ pipeline: "render",
721
+ code: `enable f16;
722
+ ${G}
723
+ @fragment fn fs() -> @location(0) vec4f {
724
+ var v: vec4<f16> = vec4<f16>(0.5h, 0.25h, 0.125h, 1.0h);
725
+ v = v * 2.0h + vec4<f16>(0.1h);
726
+ return vec4f(v);
727
+ }`
728
+ }
729
+ ];
730
+ async function ze(e, t, r) {
731
+ const n = [];
732
+ for (let i = 0; i < Q.length; i++) {
733
+ const a = Q[i];
734
+ if (r?.((i + 1) / Q.length), a.requiresFeature && !t.has(a.requiresFeature)) {
735
+ n.push({
736
+ id: a.id,
737
+ description: a.description,
738
+ skipped: !0,
739
+ compiled: !1,
740
+ pipelineCreated: !1,
741
+ messages: [{
742
+ type: "info",
743
+ message: `skipped: ${a.requiresFeature} not supported`,
744
+ lineNum: 0
745
+ }],
746
+ compileMs: 0
747
+ });
748
+ continue;
749
+ }
750
+ n.push(await Ve(e, a));
751
+ }
752
+ return n;
753
+ }
754
+ async function Ve(e, t) {
755
+ const r = {
756
+ id: t.id,
757
+ description: t.description,
758
+ skipped: !1,
759
+ compiled: !1,
760
+ pipelineCreated: !1,
761
+ messages: [],
762
+ compileMs: 0
763
+ }, n = performance.now(), i = await W(e, () => e.createShaderModule({ code: t.code }));
764
+ if (!i.ok || !i.value)
765
+ return r.compileMs = performance.now() - n, r.messages.push(...i.errors.map(ce)), r;
766
+ let a = null;
767
+ try {
768
+ a = await i.value.getCompilationInfo();
769
+ } catch (o) {
770
+ r.messages.push({ type: "error", message: String(o), lineNum: 0 });
771
+ }
772
+ if (r.compileMs = performance.now() - n, a)
773
+ for (const o of a.messages)
774
+ r.messages.push({
775
+ type: o.type,
776
+ message: o.message,
777
+ lineNum: o.lineNum
778
+ });
779
+ if (r.compiled = !r.messages.some((o) => o.type === "error"), !r.compiled) return r;
780
+ const s = await W(e, () => t.pipeline === "compute" ? e.createComputePipelineAsync({
781
+ layout: "auto",
782
+ compute: { module: i.value, entryPoint: "cs" }
783
+ }) : e.createRenderPipelineAsync({
784
+ layout: "auto",
785
+ vertex: { module: i.value, entryPoint: "vs" },
786
+ fragment: {
787
+ module: i.value,
788
+ entryPoint: "fs",
789
+ targets: [{ format: "rgba8unorm" }]
790
+ }
791
+ }));
792
+ return r.pipelineCreated = s.ok, s.ok || r.messages.push(...s.errors.map(ce)), r;
793
+ }
794
+ function ce(e) {
795
+ return { type: "error", message: e.message, lineNum: 0 };
796
+ }
797
+ const We = 10, Z = [
798
+ {
799
+ limit: "maxBufferSize",
800
+ floor: 256 * 1024 * 1024,
801
+ test: (e, t) => M(e, () => {
802
+ const r = e.createBuffer({ size: J(t), usage: GPUBufferUsage.STORAGE });
803
+ return queueMicrotask(() => v(r)), r;
804
+ })
805
+ },
806
+ {
807
+ limit: "maxStorageBufferBindingSize",
808
+ floor: 128 * 1024 * 1024,
809
+ test: (e, t) => M(e, () => {
810
+ const r = e.createBuffer({
811
+ size: J(t),
812
+ usage: GPUBufferUsage.STORAGE
813
+ }), n = e.createBindGroupLayout({
814
+ entries: [{
815
+ binding: 0,
816
+ visibility: GPUShaderStage.COMPUTE,
817
+ buffer: { type: "storage" }
818
+ }]
819
+ }), i = e.createBindGroup({
820
+ layout: n,
821
+ entries: [{ binding: 0, resource: { buffer: r, size: J(t) } }]
822
+ });
823
+ return queueMicrotask(() => v(r)), i;
824
+ })
825
+ },
826
+ {
827
+ limit: "maxUniformBufferBindingSize",
828
+ floor: 16 * 1024,
829
+ test: (e, t) => M(e, () => {
830
+ const r = e.createBuffer({
831
+ size: le(t),
832
+ usage: GPUBufferUsage.UNIFORM
833
+ }), n = e.createBindGroupLayout({
834
+ entries: [{
835
+ binding: 0,
836
+ visibility: GPUShaderStage.FRAGMENT,
837
+ buffer: { type: "uniform" }
838
+ }]
839
+ }), i = e.createBindGroup({
840
+ layout: n,
841
+ entries: [{ binding: 0, resource: { buffer: r, size: le(t) } }]
842
+ });
843
+ return queueMicrotask(() => v(r)), i;
844
+ })
845
+ },
846
+ {
847
+ limit: "maxTextureDimension2D",
848
+ floor: 2048,
849
+ test: (e, t) => M(e, () => {
850
+ const r = e.createTexture({
851
+ size: [Math.floor(t), 1],
852
+ format: "rgba8unorm",
853
+ usage: GPUTextureUsage.TEXTURE_BINDING
854
+ });
855
+ return queueMicrotask(() => v(r)), r;
856
+ })
857
+ },
858
+ {
859
+ limit: "maxTextureArrayLayers",
860
+ floor: 256,
861
+ test: (e, t) => M(e, () => {
862
+ const r = e.createTexture({
863
+ size: [4, 4, Math.floor(t)],
864
+ format: "rgba8unorm",
865
+ usage: GPUTextureUsage.TEXTURE_BINDING,
866
+ dimension: "2d"
867
+ });
868
+ return queueMicrotask(() => v(r)), r;
869
+ })
870
+ },
871
+ {
872
+ limit: "maxComputeWorkgroupStorageSize",
873
+ floor: 16 * 1024,
874
+ test: (e, t) => M(e, async () => {
875
+ const r = Math.max(1, Math.floor(t / 16)), n = e.createShaderModule({
876
+ code: `
877
+ var<workgroup> scratch: array<vec4f, ${r}>;
878
+ @group(0) @binding(0) var<storage, read_write> out: array<f32>;
879
+ @compute @workgroup_size(1) fn cs() {
880
+ scratch[0] = vec4f(1.);
881
+ out[0] = scratch[0].x;
882
+ }`
883
+ });
884
+ return e.createComputePipelineAsync({
885
+ layout: "auto",
886
+ compute: { module: n, entryPoint: "cs" }
887
+ });
888
+ })
889
+ },
890
+ {
891
+ limit: "maxComputeInvocationsPerWorkgroup",
892
+ floor: 64,
893
+ test: (e, t) => M(e, async () => {
894
+ const r = e.createShaderModule({
895
+ code: `
896
+ @group(0) @binding(0) var<storage, read_write> out: array<u32>;
897
+ @compute @workgroup_size(${Math.floor(t)}) fn cs(@builtin(local_invocation_id) lid: vec3u) {
898
+ out[0] = lid.x;
899
+ }`
900
+ });
901
+ return e.createComputePipelineAsync({
902
+ layout: "auto",
903
+ compute: { module: r, entryPoint: "cs" }
904
+ });
905
+ })
906
+ }
907
+ ];
908
+ async function je(e, t, r) {
909
+ const n = [];
910
+ for (let i = 0; i < Z.length; i++) {
911
+ const a = Z[i];
912
+ r?.((i + 1) / Z.length);
913
+ const s = t[a.limit];
914
+ if (typeof s != "number" || s <= 0) continue;
915
+ const o = await a.test(e, s);
916
+ if (o.ok) {
917
+ n.push({
918
+ limit: a.limit,
919
+ declared: s,
920
+ achieved: s,
921
+ honored: !0
922
+ });
923
+ continue;
924
+ }
925
+ const u = await He(e, a, Math.min(a.floor, s), s);
926
+ n.push({
927
+ limit: a.limit,
928
+ declared: s,
929
+ achieved: u,
930
+ honored: !1,
931
+ error: o.errors[0] ?? {
932
+ kind: "validation",
933
+ message: "refused for an unreported reason",
934
+ stage: "limit"
935
+ }
936
+ });
937
+ }
938
+ return n;
939
+ }
940
+ async function He(e, t, r, n) {
941
+ let i = r, a = (await t.test(e, i)).ok;
942
+ for (; !a && i > 1; )
943
+ i = Math.floor(i / 2), a = (await t.test(e, i)).ok;
944
+ if (!a) return 0;
945
+ let s = n;
946
+ for (let o = 0; o < We && s - i > 1; o++) {
947
+ const u = i + Math.floor((s - i) / 2);
948
+ (await t.test(e, u)).ok ? i = u : s = u;
949
+ }
950
+ return i;
951
+ }
952
+ const J = (e) => Math.floor(e / 4) * 4, le = (e) => Math.floor(e / 16) * 16;
953
+ class j {
954
+ constructor(t) {
955
+ this.available = t;
956
+ }
957
+ querySet = null;
958
+ resolveBuf = null;
959
+ readBuf = null;
960
+ static create(t) {
961
+ const r = t.features.has("timestamp-query"), n = new j(r);
962
+ if (!r) return n;
963
+ try {
964
+ n.querySet = t.createQuerySet({ type: "timestamp", count: 2 }), n.resolveBuf = t.createBuffer({
965
+ size: 16,
966
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
967
+ }), n.readBuf = t.createBuffer({
968
+ size: 16,
969
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
970
+ });
971
+ } catch {
972
+ return n.dispose(), new j(!1);
973
+ }
974
+ return n;
975
+ }
976
+ /** timestampWrites to put on a render pass descriptor */
977
+ writes() {
978
+ if (this.querySet)
979
+ return {
980
+ querySet: this.querySet,
981
+ beginningOfPassWriteIndex: 0,
982
+ endOfPassWriteIndex: 1
983
+ };
984
+ }
985
+ /** Attach the resolve commands to an encoder whose pass has been recorded */
986
+ resolve(t) {
987
+ !this.querySet || !this.resolveBuf || !this.readBuf || (t.resolveQuerySet(this.querySet, 0, 2, this.resolveBuf, 0), this.readBuf.mapState === "unmapped" && t.copyBufferToBuffer(this.resolveBuf, 0, this.readBuf, 0, 16));
988
+ }
989
+ /**
990
+ * Call after submitting. Returns GPU time in nanoseconds, or null if
991
+ * unreadable. Nanoseconds are kept raw because quantization detection needs
992
+ * the exact integer the browser reported.
993
+ */
994
+ async read() {
995
+ if (!this.readBuf || this.readBuf.mapState !== "unmapped") return null;
996
+ try {
997
+ await this.readBuf.mapAsync(GPUMapMode.READ);
998
+ const t = new BigInt64Array(this.readBuf.getMappedRange().slice(0));
999
+ this.readBuf.unmap();
1000
+ const r = Number(t[1] - t[0]);
1001
+ return !Number.isFinite(r) || r < 0 ? null : r;
1002
+ } catch {
1003
+ return null;
1004
+ }
1005
+ }
1006
+ dispose() {
1007
+ try {
1008
+ this.querySet?.destroy();
1009
+ } catch {
1010
+ }
1011
+ try {
1012
+ this.resolveBuf?.destroy();
1013
+ } catch {
1014
+ }
1015
+ try {
1016
+ this.readBuf?.destroy();
1017
+ } catch {
1018
+ }
1019
+ this.querySet = null, this.resolveBuf = null, this.readBuf = null;
1020
+ }
1021
+ }
1022
+ function Ke(e) {
1023
+ if (e.length === 0) return 0;
1024
+ const t = [...e].sort((n, i) => n - i), r = t.length >> 1;
1025
+ return t.length % 2 ? t[r] : (t[r - 1] + t[r]) / 2;
1026
+ }
1027
+ function Ye(e) {
1028
+ if (e.length < 2) return 0;
1029
+ const t = e.reduce((n, i) => n + i, 0) / e.length;
1030
+ if (t === 0) return 0;
1031
+ const r = e.reduce((n, i) => n + (i - t) ** 2, 0) / (e.length - 1);
1032
+ return Math.sqrt(r) / t;
1033
+ }
1034
+ const Xe = 0.9, Qe = 0.02, Ze = 4;
1035
+ function Je(e, t) {
1036
+ if (t <= 0 || e.length === 0) return !1;
1037
+ const r = t * Qe;
1038
+ let n = 0;
1039
+ for (const i of e) {
1040
+ const a = i % t;
1041
+ (a <= r || a >= t - r) && n++;
1042
+ }
1043
+ return n / e.length >= Xe;
1044
+ }
1045
+ function et(e) {
1046
+ const t = e.filter((s) => s > 0 && Number.isFinite(s));
1047
+ if (t.length < Ze) return null;
1048
+ const r = Math.min(...t), n = [...new Set(t)].sort((s, o) => s - o);
1049
+ let i = 1 / 0;
1050
+ for (let s = 1; s < n.length; s++)
1051
+ i = Math.min(i, n[s] - n[s - 1]);
1052
+ const a = Math.min(r, i);
1053
+ return !Number.isFinite(a) || a <= 0 ? null : Je(t, a) ? a : null;
1054
+ }
1055
+ const P = 1024, tt = 3, ee = 10, rt = 100, nt = 60, ot = 20, fe = 2048, z = `
1056
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
1057
+ var p = array<vec2f, 3>(vec2f(-1., -1.), vec2f(3., -1.), vec2f(-1., 3.));
1058
+ return vec4f(p[i], 0., 1.);
1059
+ }`, te = `
1060
+ @vertex fn vs() -> @builtin(position) vec4f {
1061
+ return vec4f(2., 2., 0.5, 1.);
1062
+ }`, V = `
1063
+ @fragment fn fs() -> @location(0) vec4f { return vec4f(0.25, 0.5, 0.75, 1.); }`, re = {
1064
+ color: { srcFactor: "one", dstFactor: "one", operation: "add" },
1065
+ alpha: { srcFactor: "one", dstFactor: "one", operation: "add" }
1066
+ }, de = "rgba8unorm";
1067
+ function B(e, t, r) {
1068
+ const n = typeof t == "string" ? e.createShaderModule({ code: t }) : t;
1069
+ return e.createRenderPipelineAsync({
1070
+ layout: "auto",
1071
+ vertex: { module: n, entryPoint: "vs" },
1072
+ fragment: {
1073
+ module: n,
1074
+ entryPoint: "fs",
1075
+ targets: [r ? { format: de, blend: r } : { format: de }]
1076
+ }
1077
+ });
1078
+ }
1079
+ const C = 2e3, pe = 1e5, ne = [
1080
+ {
1081
+ id: "drawcall-overhead",
1082
+ description: "Empty draw calls — browser validation and driver call cost",
1083
+ unitWorkload: C,
1084
+ unit: "draws/s",
1085
+ timingMode: "wall-clock-only",
1086
+ setup: async (e, t) => {
1087
+ const r = await B(e, te + V);
1088
+ return {
1089
+ record(n, i, a) {
1090
+ const s = $(n, t, a);
1091
+ s.setPipeline(r);
1092
+ const o = C * i;
1093
+ for (let u = 0; u < o; u++) s.draw(3);
1094
+ s.end();
1095
+ }
1096
+ };
1097
+ }
1098
+ },
1099
+ {
1100
+ id: "pipeline-switch",
1101
+ description: "Alternating between 8 pipelines per draw — state change cost",
1102
+ unitWorkload: C,
1103
+ unit: "switches/s",
1104
+ timingMode: "wall-clock-only",
1105
+ setup: async (e, t) => {
1106
+ const r = [];
1107
+ for (let n = 0; n < 8; n++)
1108
+ r.push(await B(e, te + `
1109
+ @fragment fn fs() -> @location(0) vec4f { return vec4f(${(n / 8).toFixed(3)}, 0.5, 0.75, 1.); }`));
1110
+ return {
1111
+ record(n, i, a) {
1112
+ const s = $(n, t, a), o = C * i;
1113
+ for (let u = 0; u < o; u++)
1114
+ s.setPipeline(r[u & 7]), s.draw(3);
1115
+ s.end();
1116
+ }
1117
+ };
1118
+ }
1119
+ },
1120
+ {
1121
+ id: "bindgroup-switch",
1122
+ description: "Alternating between 64 bind groups per draw — resource binding cost",
1123
+ unitWorkload: C,
1124
+ unit: "binds/s",
1125
+ timingMode: "wall-clock-only",
1126
+ setup: async (e, t) => {
1127
+ const r = await B(e, `
1128
+ struct U { tint: vec4f };
1129
+ @group(0) @binding(0) var<uniform> u: U;
1130
+ ${te}
1131
+ @fragment fn fs() -> @location(0) vec4f { return u.tint; }`), n = r.getBindGroupLayout(0), i = [], a = [];
1132
+ for (let s = 0; s < 64; s++) {
1133
+ const o = e.createBuffer({
1134
+ size: 16,
1135
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1136
+ });
1137
+ e.queue.writeBuffer(o, 0, new Float32Array([s / 64, 0.5, 0.75, 1])), i.push(o), a.push(e.createBindGroup({
1138
+ layout: n,
1139
+ entries: [{ binding: 0, resource: { buffer: o } }]
1140
+ }));
1141
+ }
1142
+ return {
1143
+ record(s, o, u) {
1144
+ const c = $(s, t, u);
1145
+ c.setPipeline(r);
1146
+ const l = C * o;
1147
+ for (let f = 0; f < l; f++)
1148
+ c.setBindGroup(0, a[f & 63]), c.draw(3);
1149
+ c.end();
1150
+ },
1151
+ dispose: () => v(...i)
1152
+ };
1153
+ }
1154
+ },
1155
+ {
1156
+ id: "fillrate",
1157
+ description: `${P}x${P} fullscreen overdraw, blended — fragment throughput`,
1158
+ unitWorkload: P * P * 8,
1159
+ unit: "MPixel/s",
1160
+ timingMode: "gpu-preferred",
1161
+ setup: async (e, t) => {
1162
+ const r = await B(e, z + V, re);
1163
+ return {
1164
+ record(n, i, a) {
1165
+ const s = $(n, t, a);
1166
+ s.setPipeline(r);
1167
+ const o = 8 * i;
1168
+ for (let u = 0; u < o; u++) s.draw(3);
1169
+ s.end();
1170
+ }
1171
+ };
1172
+ }
1173
+ },
1174
+ {
1175
+ id: "fragment-alu",
1176
+ description: "Heavy per-pixel arithmetic — shader math, separated from fill rate",
1177
+ unitWorkload: P * P,
1178
+ unit: "MPixel/s",
1179
+ timingMode: "gpu-preferred",
1180
+ setup: async (e, t) => {
1181
+ const r = e.createShaderModule({
1182
+ code: z + `
1183
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
1184
+ var p = pos.xyz * 0.001;
1185
+ var acc = 0.;
1186
+ for (var i = 0u; i < 64u; i++) {
1187
+ p = fract(p * 1.7 + vec3f(0.31, 0.17, 0.53));
1188
+ acc += dot(p, vec3f(0.33)) * exp(-p.x) + sqrt(abs(p.y));
1189
+ }
1190
+ return vec4f(acc * 0.01, p.y, p.z, 1.);
1191
+ }`
1192
+ }), n = await B(e, r, re);
1193
+ return {
1194
+ record(i, a, s) {
1195
+ const o = $(i, t, s);
1196
+ o.setPipeline(n);
1197
+ for (let u = 0; u < a; u++) o.draw(3);
1198
+ o.end();
1199
+ }
1200
+ };
1201
+ }
1202
+ },
1203
+ {
1204
+ id: "triangle-throughput",
1205
+ description: "Many small triangles — geometry throughput",
1206
+ unitWorkload: pe,
1207
+ unit: "MTri/s",
1208
+ timingMode: "gpu-preferred",
1209
+ setup: async (e, t) => {
1210
+ const r = e.createShaderModule({
1211
+ code: `
1212
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
1213
+ let tri = vi / 3u;
1214
+ let corner = vi % 3u;
1215
+ // Scatter triangles across a grid — they must stay on screen to avoid culling.
1216
+ let gx = f32(tri % 1000u) / 1000. * 2. - 1.;
1217
+ let gy = f32((tri / 1000u) % 100u) / 100. * 2. - 1.;
1218
+ var off = array<vec2f, 3>(vec2f(0., 0.), vec2f(0.0015, 0.), vec2f(0., 0.0015));
1219
+ return vec4f(gx + off[corner].x, gy + off[corner].y, 0.5, 1.);
1220
+ }
1221
+ ${V}`
1222
+ }), n = await B(e, r);
1223
+ return {
1224
+ record(i, a, s) {
1225
+ const o = $(i, t, s);
1226
+ o.setPipeline(n);
1227
+ for (let u = 0; u < a; u++) o.draw(pe * 3);
1228
+ o.end();
1229
+ }
1230
+ };
1231
+ }
1232
+ },
1233
+ {
1234
+ id: "texture-sampling",
1235
+ description: "32 texture samples per pixel — texture bandwidth",
1236
+ unitWorkload: P * P * 32,
1237
+ unit: "GSample/s",
1238
+ timingMode: "gpu-preferred",
1239
+ setup: async (e, t) => {
1240
+ const r = e.createTexture({
1241
+ size: [512, 512],
1242
+ format: "rgba8unorm",
1243
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1244
+ }), n = new Uint8Array(512 * 512 * 4);
1245
+ for (let u = 0; u < n.length; u++) n[u] = u * 37 & 255;
1246
+ e.queue.writeTexture({ texture: r }, n, { bytesPerRow: 512 * 4 }, [512, 512]);
1247
+ const i = e.createShaderModule({
1248
+ code: `
1249
+ @group(0) @binding(0) var t: texture_2d<f32>;
1250
+ @group(0) @binding(1) var s: sampler;
1251
+ ${z}
1252
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
1253
+ var acc = vec4f(0.);
1254
+ let base = pos.xy / ${P}.;
1255
+ for (var i = 0u; i < 32u; i++) {
1256
+ // Spread the coordinates so this does not all sit in cache.
1257
+ let uv = fract(base + vec2f(f32(i) * 0.137, f32(i) * 0.379));
1258
+ acc += textureSampleLevel(t, s, uv, 0.);
1259
+ }
1260
+ return acc * 0.03125;
1261
+ }`
1262
+ }), a = await B(e, i, re), s = e.createSampler({ magFilter: "linear", minFilter: "linear" }), o = e.createBindGroup({
1263
+ layout: a.getBindGroupLayout(0),
1264
+ entries: [
1265
+ { binding: 0, resource: r.createView() },
1266
+ { binding: 1, resource: s }
1267
+ ]
1268
+ });
1269
+ return {
1270
+ record(u, c, l) {
1271
+ const f = $(u, t, l);
1272
+ f.setPipeline(a), f.setBindGroup(0, o);
1273
+ for (let p = 0; p < c; p++) f.draw(3);
1274
+ f.end();
1275
+ },
1276
+ dispose: () => v(r)
1277
+ };
1278
+ }
1279
+ }
1280
+ ];
1281
+ function it() {
1282
+ let e = 1 / 0;
1283
+ for (let t = 0; t < 24; t++) {
1284
+ const r = performance.now();
1285
+ let n = r;
1286
+ for (; n === r; ) n = performance.now();
1287
+ e = Math.min(e, n - r);
1288
+ }
1289
+ return Number.isFinite(e) && e > 0 ? e : 0;
1290
+ }
1291
+ async function at(e, t) {
1292
+ if (!t.available) return null;
1293
+ const r = e.createTexture({
1294
+ size: [256, 256],
1295
+ format: "rgba8unorm",
1296
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
1297
+ }), n = r.createView();
1298
+ let i;
1299
+ try {
1300
+ i = await B(e, z + V);
1301
+ } catch {
1302
+ return v(r), null;
1303
+ }
1304
+ const a = [];
1305
+ let s = 1;
1306
+ for (let o = 0; o < 14; o++) {
1307
+ let u = 0;
1308
+ for (let c = 0; c < 6; c++) {
1309
+ const l = e.createCommandEncoder(), f = t.writes(), p = $(l, n, f);
1310
+ p.setPipeline(i);
1311
+ for (let w = 0; w < s; w++) p.draw(3);
1312
+ p.end(), f && t.resolve(l), e.queue.submit([l.finish()]), await e.queue.onSubmittedWorkDone();
1313
+ const h = await t.read();
1314
+ h !== null && (a.push(h), h > 0 && u++);
1315
+ }
1316
+ if (u >= 4 && new Set(a.filter((c) => c > 0)).size >= 2) break;
1317
+ s *= 4;
1318
+ }
1319
+ return v(r), et(a);
1320
+ }
1321
+ async function st(e, t, r) {
1322
+ const n = performance.now(), i = j.create(e), a = e.createTexture({
1323
+ size: [P, P],
1324
+ format: "rgba8unorm",
1325
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
1326
+ }), s = a.createView(), o = await at(e, i), u = it(), c = [];
1327
+ for (let l = 0; l < ne.length; l++)
1328
+ c.push(await ut(
1329
+ e,
1330
+ ne[l],
1331
+ s,
1332
+ i,
1333
+ t,
1334
+ o,
1335
+ u
1336
+ )), r?.((l + 1) / ne.length);
1337
+ return i.dispose(), v(a), {
1338
+ results: c,
1339
+ timestampQuery: i.available,
1340
+ timerResolutionNs: o,
1341
+ wallClockResolutionMs: u > 0 ? u : null,
1342
+ totalMs: Math.round(performance.now() - n)
1343
+ };
1344
+ }
1345
+ async function ut(e, t, r, n, i, a, s) {
1346
+ const o = {
1347
+ id: t.id,
1348
+ description: t.description,
1349
+ medianMs: 0,
1350
+ minMs: 0,
1351
+ variation: 0,
1352
+ timing: "wall-clock",
1353
+ samples: 0,
1354
+ repetitions: 0
1355
+ };
1356
+ let u;
1357
+ try {
1358
+ u = await t.setup(e, r);
1359
+ } catch (l) {
1360
+ return { ...o, failed: `setup failed: ${he(l)}` };
1361
+ }
1362
+ const c = t.timingMode === "gpu-preferred" && n.available;
1363
+ try {
1364
+ for (let m = 0; m < tt; m++) {
1365
+ const S = e.createCommandEncoder();
1366
+ u.record(S, 1), e.queue.submit([S.finish()]);
1367
+ }
1368
+ await e.queue.onSubmittedWorkDone();
1369
+ const l = c ? a ? Math.max(ee, a * rt / 1e6) : ee : Math.max(ee, s * nt);
1370
+ let f = 1;
1371
+ for (let m = 0; m < 8; m++) {
1372
+ const { ms: S } = await me(e, u, f, n, c);
1373
+ if (S >= l || f >= fe) break;
1374
+ const U = S > 1e-3 ? Math.ceil(l / S) : 8;
1375
+ f = Math.min(fe, f * Math.max(2, Math.min(16, U)));
1376
+ }
1377
+ const p = [];
1378
+ let h = 0;
1379
+ for (let m = 0; m < i; m++) {
1380
+ const { ms: S, fromGpu: U } = await me(e, u, f, n, c);
1381
+ p.push(S), U && h++;
1382
+ }
1383
+ if (u.dispose?.(), p.length === 0)
1384
+ return { ...o, failed: "no measurements were obtained" };
1385
+ const w = h > p.length / 2, g = w ? p : ct(p), T = Ke(g), E = t.unitWorkload * f, k = {
1386
+ ...o,
1387
+ medianMs: q(T, 4),
1388
+ minMs: q(Math.min(...g), 4),
1389
+ variation: q(Ye(g), 3),
1390
+ timing: w ? "timestamp-query" : "wall-clock",
1391
+ samples: g.length,
1392
+ repetitions: f
1393
+ }, R = w ? a != null ? a / 1e6 : null : s > 0 ? s : null;
1394
+ if (R) {
1395
+ const m = T / R;
1396
+ k.ticks = q(m, 1), k.quantized = m < ot;
1397
+ }
1398
+ if (T > 0) {
1399
+ const m = E / (T / 1e3);
1400
+ k.throughput = q(lt(m, t.unit), 2), k.throughputUnit = t.unit;
1401
+ }
1402
+ return k;
1403
+ } catch (l) {
1404
+ return u.dispose?.(), { ...o, failed: `run failed: ${he(l)}` };
1405
+ }
1406
+ }
1407
+ async function me(e, t, r, n, i) {
1408
+ const a = e.createCommandEncoder(), s = i ? n.writes() : void 0, o = performance.now();
1409
+ t.record(a, r, s), s && n.resolve(a), e.queue.submit([a.finish()]), await e.queue.onSubmittedWorkDone();
1410
+ const u = performance.now() - o;
1411
+ if (s) {
1412
+ const c = await n.read();
1413
+ if (c !== null && c > 0) return { ms: c / 1e6, fromGpu: !0 };
1414
+ }
1415
+ return { ms: u, fromGpu: !1 };
1416
+ }
1417
+ function ct(e) {
1418
+ return e.length < 5 ? e : [...e].sort((r, n) => r - n).slice(1, -1);
1419
+ }
1420
+ function $(e, t, r) {
1421
+ const n = {
1422
+ colorAttachments: [{
1423
+ view: t,
1424
+ loadOp: "clear",
1425
+ storeOp: "store",
1426
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
1427
+ }]
1428
+ };
1429
+ return r && (n.timestampWrites = r), e.beginRenderPass(n);
1430
+ }
1431
+ function lt(e, t) {
1432
+ return t.startsWith("M") ? e / 1e6 : t.startsWith("G") ? e / 1e9 : e;
1433
+ }
1434
+ function q(e, t) {
1435
+ const r = 10 ** t;
1436
+ return Math.round(e * r) / r;
1437
+ }
1438
+ function he(e) {
1439
+ return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
1440
+ }
1441
+ function ft(e, t, r, n, i) {
1442
+ const a = [], s = Ge(i);
1443
+ for (const o of e) {
1444
+ const u = $e(o.format);
1445
+ if (o.featureDeclared && !o.creatable) {
1446
+ a.push({
1447
+ kind: "format-declared-not-usable",
1448
+ subject: o.format,
1449
+ detail: o.requiresFeature ? `${o.requiresFeature} is declared but createTexture fails: ${O(o.errors)}` : `core format, yet createTexture fails: ${O(o.errors)}`,
1450
+ severity: "breaking"
1451
+ });
1452
+ continue;
1453
+ }
1454
+ if (o.creatable && !o.sampleable && a.push({
1455
+ kind: "format-declared-not-usable",
1456
+ subject: o.format,
1457
+ detail: `the texture is created but shader sampling fails: ${O(o.errors)}`,
1458
+ severity: "breaking"
1459
+ }), !o.featureDeclared && o.creatable && a.push({
1460
+ kind: "format-usable-not-declared",
1461
+ subject: o.format,
1462
+ detail: `${o.requiresFeature} is not declared, yet texture creation succeeds`,
1463
+ severity: "note"
1464
+ }), !u || !o.creatable) continue;
1465
+ const c = _e(u, i);
1466
+ c.renderable && !o.renderable && a.push({
1467
+ kind: "format-declared-not-usable",
1468
+ subject: o.format,
1469
+ detail: `should be a valid render target per spec, but the render pass fails: ${O(o.errors)}`,
1470
+ severity: "breaking"
1471
+ }), c.blendable && o.renderable && !o.blendable && a.push({
1472
+ kind: "format-declared-not-usable",
1473
+ subject: o.format,
1474
+ detail: "should support blending per spec, but creating a blend pipeline fails",
1475
+ severity: "degraded"
1476
+ }), c.storage && !o.storageWritable && a.push({
1477
+ kind: "format-declared-not-usable",
1478
+ subject: o.format,
1479
+ detail: `should work as a storage texture per spec, but fails: ${O(o.errors)}`,
1480
+ severity: "breaking"
1481
+ }), !c.storage && o.storageWritable && !s && a.push({
1482
+ kind: "format-usable-not-declared",
1483
+ subject: o.format,
1484
+ detail: "not a storage format per spec, yet it works on this implementation",
1485
+ severity: "note"
1486
+ });
1487
+ }
1488
+ for (const o of r) {
1489
+ if (o.honored) continue;
1490
+ const u = o.declared > 0 ? o.achieved / o.declared : 0;
1491
+ a.push({
1492
+ kind: "limit-not-honored",
1493
+ subject: o.limit,
1494
+ detail: `declares ${be(o.declared)} but only reaches ${be(o.achieved)}` + ` (${(u * 100).toFixed(0)}%). ${o.error?.message ?? ""}`.trimEnd(),
1495
+ // Below half, code written against the declared value simply dies.
1496
+ severity: u < 0.5 ? "breaking" : "degraded"
1497
+ });
1498
+ }
1499
+ for (const o of t)
1500
+ o.skipped || (o.compiled ? o.pipelineCreated || a.push({
1501
+ kind: "shader-pipeline-failure",
1502
+ subject: o.id,
1503
+ detail: `${o.description} — compiled, but pipeline creation failed: ${ge(o)}`,
1504
+ severity: "breaking"
1505
+ }) : a.push({
1506
+ kind: "shader-compile-failure",
1507
+ subject: o.id,
1508
+ detail: `${o.description} — compilation failed: ${ge(o)}`,
1509
+ severity: "breaking"
1510
+ }));
1511
+ if (n)
1512
+ for (const o of n.results)
1513
+ o.failed ? a.push({
1514
+ kind: "performance-cliff",
1515
+ subject: o.id,
1516
+ detail: `the benchmark did not complete: ${o.failed}`,
1517
+ severity: "degraded"
1518
+ }) : o.quantized ? a.push({
1519
+ kind: "performance-cliff",
1520
+ subject: o.id,
1521
+ detail: `the measurement spans only ${o.ticks} timer resolution units, so it sits on the quantization floor. Treat this throughput as a lower bound rather than a measurement`,
1522
+ severity: "note"
1523
+ }) : o.variation > 0.35 && a.push({
1524
+ kind: "performance-cliff",
1525
+ subject: o.id,
1526
+ detail: `variation is ${(o.variation * 100).toFixed(0)}% — likely thermal throttling or external load. This number should not be trusted`,
1527
+ severity: "note"
1528
+ });
1529
+ return a;
1530
+ }
1531
+ function ge(e) {
1532
+ return e.messages.find((t) => t.type === "error")?.message ?? "no error message";
1533
+ }
1534
+ function be(e) {
1535
+ return e >= 1024 * 1024 * 1024 ? `${(e / 1024 / 1024 / 1024).toFixed(2)}GB` : e >= 1024 * 1024 ? `${(e / 1024 / 1024).toFixed(1)}MB` : e >= 1024 ? `${(e / 1024).toFixed(1)}KB` : String(e);
1536
+ }
1537
+ const dt = 16;
1538
+ function pt(e) {
1539
+ const t = e.browserVersion.split(".")[0] ?? "";
1540
+ return [
1541
+ e.browser,
1542
+ t,
1543
+ e.vendor,
1544
+ e.architecture,
1545
+ e.device,
1546
+ e.description
1547
+ ].join("|");
1548
+ }
1549
+ async function ve(e) {
1550
+ const t = pt(e), r = globalThis.crypto?.subtle;
1551
+ if (r)
1552
+ try {
1553
+ const n = await r.digest("SHA-256", new TextEncoder().encode(t));
1554
+ return ht(new Uint8Array(n).subarray(0, dt));
1555
+ } catch {
1556
+ }
1557
+ return mt(t);
1558
+ }
1559
+ function mt(e) {
1560
+ const r = [2166136261, 16777619, 3421674724, 2216829733];
1561
+ for (let n = 0; n < e.length; n++) {
1562
+ const i = e.charCodeAt(n);
1563
+ for (let a = 0; a < r.length; a++)
1564
+ r[a] ^= i + a * 40503, r[a] = Math.imul(r[a], 16777619) >>> 0;
1565
+ }
1566
+ return r.map((n) => n.toString(16).padStart(8, "0")).join("");
1567
+ }
1568
+ function ht(e) {
1569
+ let t = "";
1570
+ for (const r of e) t += r.toString(16).padStart(2, "0");
1571
+ return t;
1572
+ }
1573
+ const F = { formats: 0.25, shaders: 0.1, limits: 0.15, bench: 0.5 };
1574
+ async function At(e = {}) {
1575
+ const {
1576
+ powerPreference: t,
1577
+ benchmark: r = !0,
1578
+ onProgress: n,
1579
+ formats: i
1580
+ } = e, a = gt(e.benchSamples ?? 7, 1, 99), s = performance.now(), o = await Te(), u = {
1581
+ schema: 4,
1582
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1583
+ fingerprint: "",
1584
+ environment: o,
1585
+ adapter: null,
1586
+ declared: null,
1587
+ verified: null,
1588
+ benchmarks: null,
1589
+ discrepancies: [],
1590
+ elapsedMs: 0
1591
+ };
1592
+ let c;
1593
+ try {
1594
+ c = await ke(t);
1595
+ } catch (b) {
1596
+ return {
1597
+ ...u,
1598
+ unavailable: b instanceof D ? b.message : oe(b),
1599
+ fingerprint: await ve({
1600
+ browser: o.browser,
1601
+ browserVersion: o.browserVersion,
1602
+ vendor: "",
1603
+ architecture: "",
1604
+ device: "",
1605
+ description: ""
1606
+ }),
1607
+ elapsedMs: performance.now() - s
1608
+ };
1609
+ }
1610
+ const { device: l, identity: f, declared: p, denied: h, lost: w } = c;
1611
+ let g;
1612
+ w.then((b) => {
1613
+ g = `${b.reason}: ${b.message}`;
1614
+ });
1615
+ const T = new Set(p.features);
1616
+ let E = 0;
1617
+ const k = (b, K) => {
1618
+ try {
1619
+ n?.(b, K);
1620
+ } catch {
1621
+ }
1622
+ }, R = (b, K) => (xe) => k(b, E + K * xe), m = {
1623
+ formats: [],
1624
+ shaders: [],
1625
+ limits: [],
1626
+ deviceLost: !1
1627
+ };
1628
+ try {
1629
+ k("formats", E), m.formats = await Ce(
1630
+ l,
1631
+ T,
1632
+ i,
1633
+ R("formats", F.formats)
1634
+ ), E += F.formats, k("shaders", E), m.shaders = await ze(
1635
+ l,
1636
+ T,
1637
+ R("shaders", F.shaders)
1638
+ ), E += F.shaders, k("limits", E), m.limits = await je(
1639
+ l,
1640
+ p.limits,
1641
+ R("limits", F.limits)
1642
+ ), E += F.limits;
1643
+ } catch (b) {
1644
+ m.deviceLost = !0, m.deviceLostReason = g ?? oe(b);
1645
+ }
1646
+ let S = null;
1647
+ if (r && !m.deviceLost) {
1648
+ k("benchmarks", E);
1649
+ try {
1650
+ S = await st(l, a, R("benchmarks", F.bench));
1651
+ } catch (b) {
1652
+ m.deviceLostReason = g ?? oe(b);
1653
+ }
1654
+ }
1655
+ k("done", 1), g && (m.deviceLost = !0, m.deviceLostReason = g);
1656
+ const U = ft(
1657
+ m.formats,
1658
+ m.shaders,
1659
+ m.limits,
1660
+ S,
1661
+ T
1662
+ );
1663
+ for (const b of h)
1664
+ U.push({
1665
+ kind: "limit-not-honored",
1666
+ subject: b,
1667
+ detail: "requestDevice refused values the adapter itself advertised",
1668
+ severity: "degraded"
1669
+ });
1670
+ const we = {
1671
+ ...u,
1672
+ fingerprint: await ve({
1673
+ browser: o.browser,
1674
+ browserVersion: o.browserVersion,
1675
+ vendor: f.vendor,
1676
+ architecture: f.architecture,
1677
+ device: f.device,
1678
+ description: f.description
1679
+ }),
1680
+ adapter: f,
1681
+ declared: p,
1682
+ verified: m,
1683
+ benchmarks: S,
1684
+ discrepancies: U,
1685
+ elapsedMs: Math.round(performance.now() - s)
1686
+ };
1687
+ return l.destroy(), we;
1688
+ }
1689
+ function gt(e, t, r) {
1690
+ return Number.isFinite(e) ? Math.min(r, Math.max(t, Math.round(e))) : t;
1691
+ }
1692
+ function oe(e) {
1693
+ return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
1694
+ }
1695
+ function bt(e) {
1696
+ return {
1697
+ profile: e,
1698
+ features: new Set(e.declared.features),
1699
+ formats: new Map(e.verified.formats.map((t) => [t.format, t])),
1700
+ limits: new Map(e.verified.limits.map((t) => [t.limit, t])),
1701
+ benchmarks: new Map((e.benchmarks?.results ?? []).map((t) => [t.id, t]))
1702
+ };
1703
+ }
1704
+ const vt = [
1705
+ "creatable",
1706
+ "sampleable",
1707
+ "renderable",
1708
+ "blendable",
1709
+ "storageWritable",
1710
+ "multisample4x"
1711
+ ], yt = 0.15;
1712
+ function Bt(e) {
1713
+ const t = [], r = [], n = [], i = /* @__PURE__ */ new Set();
1714
+ e.forEach((s, o) => {
1715
+ if (s.unavailable) {
1716
+ n.push({ index: o, reason: `WebGPU unavailable: ${s.unavailable}` });
1717
+ return;
1718
+ }
1719
+ if (!s.verified || !s.declared) {
1720
+ n.push({ index: o, reason: "profile has no verified data" });
1721
+ return;
1722
+ }
1723
+ if (i.has(s.fingerprint)) {
1724
+ n.push({
1725
+ index: o,
1726
+ reason: `duplicate of an earlier profile (${s.fingerprint.slice(0, 8)}) — comparison keys values by fingerprint and cannot hold two`
1727
+ });
1728
+ return;
1729
+ }
1730
+ i.add(s.fingerprint);
1731
+ const u = typeof s.schema == "number" ? s.schema : 0;
1732
+ t.push({
1733
+ fingerprint: s.fingerprint,
1734
+ label: wt(s),
1735
+ mobile: s.environment.mobile,
1736
+ index: o,
1737
+ schema: u,
1738
+ staleBenchmarks: u < 2
1739
+ }), r.push(s);
1740
+ });
1741
+ const a = r.map(bt);
1742
+ return {
1743
+ devices: t,
1744
+ ...xt(a),
1745
+ formats: kt(a),
1746
+ limits: St(a),
1747
+ benchmarks: Mt(a),
1748
+ excluded: n
1749
+ };
1750
+ }
1751
+ function wt(e) {
1752
+ const t = [e.adapter?.vendor, e.adapter?.architecture].filter(Boolean).join(" ") || e.adapter?.description || "unknown GPU", r = [e.environment.browser, Pt(e.environment.browserVersion)].filter(Boolean).join(" ");
1753
+ return r ? `${t} / ${r}` : t;
1754
+ }
1755
+ function xt(e) {
1756
+ const t = /* @__PURE__ */ new Set();
1757
+ for (const i of e) for (const a of i.features) t.add(a);
1758
+ const r = [], n = [];
1759
+ for (const i of [...t].sort()) {
1760
+ const a = [], s = [];
1761
+ for (const o of e)
1762
+ (o.features.has(i) ? a : s).push(o.profile.fingerprint);
1763
+ s.length === 0 ? n.push(i) : r.push({ feature: i, supportedBy: a, missingFrom: s });
1764
+ }
1765
+ return { features: r, sharedFeatures: n };
1766
+ }
1767
+ function kt(e) {
1768
+ const t = /* @__PURE__ */ new Set();
1769
+ for (const n of e)
1770
+ for (const i of n.formats.keys()) t.add(i);
1771
+ const r = [];
1772
+ for (const n of [...t].sort())
1773
+ for (const i of vt) {
1774
+ const a = [], s = [];
1775
+ for (const o of e) {
1776
+ const u = o.formats.get(n);
1777
+ u && (u[i] ? a : s).push(o.profile.fingerprint);
1778
+ }
1779
+ a.length > 0 && s.length > 0 && r.push({ format: n, capability: i, supportedBy: a, missingFrom: s });
1780
+ }
1781
+ return r;
1782
+ }
1783
+ function St(e) {
1784
+ const t = /* @__PURE__ */ new Set();
1785
+ for (const n of e) for (const i of n.limits.keys()) t.add(i);
1786
+ const r = [];
1787
+ for (const n of [...t].sort()) {
1788
+ const i = {};
1789
+ for (const u of e) {
1790
+ const c = u.limits.get(n);
1791
+ c && (i[u.profile.fingerprint] = c.achieved);
1792
+ }
1793
+ const a = Object.values(i);
1794
+ if (a.length < 2) continue;
1795
+ const s = Math.min(...a), o = Math.max(...a);
1796
+ s !== o && r.push({ limit: n, values: i, min: s, max: o, ratio: s > 0 ? o / s : 1 / 0 });
1797
+ }
1798
+ return r.sort((n, i) => i.ratio - n.ratio);
1799
+ }
1800
+ function Mt(e) {
1801
+ const t = /* @__PURE__ */ new Map();
1802
+ for (const n of e)
1803
+ for (const [i, a] of n.benchmarks)
1804
+ t.has(i) || t.set(i, a);
1805
+ const r = [];
1806
+ for (const [n, i] of t) {
1807
+ const a = {}, s = [];
1808
+ for (const f of e) {
1809
+ const p = f.profile.fingerprint, h = f.benchmarks.get(n);
1810
+ if (!h || h.failed || h.throughput == null) {
1811
+ a[p] = null;
1812
+ continue;
1813
+ }
1814
+ a[p] = h.throughput, ((f.profile.schema ?? 0) < 2 || h.quantized || h.variation > yt) && s.push(p);
1815
+ }
1816
+ const o = Object.entries(a).filter((f) => f[1] != null);
1817
+ let u = null, c = null, l = null;
1818
+ if (o.length >= 2) {
1819
+ const f = [...o].sort((w, g) => g[1] - w[1]);
1820
+ u = f[0][0], c = f[f.length - 1][0];
1821
+ const p = f[0][1], h = f[f.length - 1][1];
1822
+ l = h > 0 ? p / h : null;
1823
+ }
1824
+ r.push({
1825
+ id: n,
1826
+ description: i.description,
1827
+ unit: i.throughputUnit ?? "",
1828
+ values: a,
1829
+ fastest: u,
1830
+ slowest: c,
1831
+ ratio: l,
1832
+ unreliable: s
1833
+ });
1834
+ }
1835
+ return r.sort((n, i) => (i.ratio ?? 0) - (n.ratio ?? 0));
1836
+ }
1837
+ function $t(e, t = {}) {
1838
+ const r = Math.max(1, t.limit ?? 20), n = [], i = (o) => o.slice(0, 8), a = (o) => ({
1839
+ shown: o.slice(0, r),
1840
+ hidden: Math.max(0, o.length - r)
1841
+ }), s = (o, u) => {
1842
+ o > 0 && n.push(` ... and ${o} more ${u}`);
1843
+ };
1844
+ n.push("Devices");
1845
+ for (const o of e.devices) {
1846
+ const u = [
1847
+ o.mobile ? "(mobile)" : "",
1848
+ o.staleBenchmarks ? `(schema ${o.schema} - benchmarks not comparable)` : ""
1849
+ ].filter(Boolean).join(" ");
1850
+ n.push(` ${i(o.fingerprint)} ${o.label}${u ? " " + u : ""}`);
1851
+ }
1852
+ if (e.benchmarks.length > 0) {
1853
+ n.push("", "Performance");
1854
+ const { shown: o, hidden: u } = a(e.benchmarks);
1855
+ for (const c of o) {
1856
+ const l = c.ratio ? `${c.ratio.toFixed(1)}x` : "—";
1857
+ n.push(` ${c.id} (${c.unit}) gap ${l}`);
1858
+ for (const f of e.devices) {
1859
+ const p = c.values[f.fingerprint], h = c.unreliable.includes(f.fingerprint) ? " [unreliable]" : "";
1860
+ n.push(` ${i(f.fingerprint)} ${p != null ? p.toLocaleString() : "—"}${h}`);
1861
+ }
1862
+ }
1863
+ s(u, "benchmarks");
1864
+ }
1865
+ if (e.features.length > 0) {
1866
+ n.push("", "Features not available everywhere");
1867
+ const { shown: o, hidden: u } = a(e.features);
1868
+ for (const c of o)
1869
+ n.push(` ${c.feature} missing on ${c.missingFrom.map(i).join(", ")}`);
1870
+ s(u, "features");
1871
+ }
1872
+ if (e.formats.length > 0) {
1873
+ n.push("", "Format capabilities that differ");
1874
+ const { shown: o, hidden: u } = a(e.formats);
1875
+ for (const c of o)
1876
+ n.push(` ${c.format}.${c.capability} missing on ${c.missingFrom.map(i).join(", ")}`);
1877
+ s(u, "format capabilities");
1878
+ }
1879
+ if (e.limits.length > 0) {
1880
+ n.push("", "Limits that differ");
1881
+ const { shown: o, hidden: u } = a(e.limits);
1882
+ for (const c of o) {
1883
+ const l = Number.isFinite(c.ratio) ? `${c.ratio.toFixed(1)}x` : "—", f = e.devices.map((p) => `${i(p.fingerprint)}=${c.values[p.fingerprint]?.toLocaleString() ?? "—"}`).join(" ");
1884
+ n.push(` ${c.limit} gap ${l} ${f}`);
1885
+ }
1886
+ s(u, "limits");
1887
+ }
1888
+ if (e.excluded.length > 0) {
1889
+ n.push("", "Excluded");
1890
+ for (const o of e.excluded) n.push(` profile #${o.index}: ${o.reason}`);
1891
+ }
1892
+ return n.join(`
1893
+ `);
1894
+ }
1895
+ function Pt(e) {
1896
+ return e.split(".")[0] ?? "";
1897
+ }
1898
+ function Rt(e) {
1899
+ return e.unavailable !== void 0;
1900
+ }
1901
+ function _t(e) {
1902
+ return e.discrepancies.filter((t) => t.severity === "breaking");
1903
+ }
1904
+ function Gt(e) {
1905
+ return (e.verified?.formats ?? []).filter((t) => t.renderable).map((t) => t.format);
1906
+ }
1907
+ function Ft(e) {
1908
+ return (e.verified?.formats ?? []).filter((t) => t.sampleable).map((t) => t.format);
1909
+ }
1910
+ function Ut(e, t, r = "render") {
1911
+ const n = e.verified?.formats ?? [];
1912
+ for (const i of t) {
1913
+ const a = n.find((s) => s.format === i);
1914
+ if (a && (r === "render" && a.renderable || r === "sample" && a.sampleable || r === "storage" && a.storageWritable))
1915
+ return i;
1916
+ }
1917
+ return null;
1918
+ }
1919
+ function Ct(e, t) {
1920
+ return e.benchmarks?.results.find((r) => r.id === t) ?? null;
1921
+ }
1922
+ export {
1923
+ ie as FORMATS,
1924
+ Tt as MIN_COMPARABLE_BENCHMARK_SCHEMA,
1925
+ Et as SCHEMA_VERSION,
1926
+ Ct as benchmark,
1927
+ _t as breakingIssues,
1928
+ Bt as compareProfiles,
1929
+ wt as describeDevice,
1930
+ $e as findMeta,
1931
+ $t as formatComparison,
1932
+ Rt as isUnavailable,
1933
+ Ut as pickFormat,
1934
+ At as probe,
1935
+ Gt as renderableFormats,
1936
+ Ft as sampleableFormats
1937
+ };
1938
+ //# sourceMappingURL=gpu-atlas.js.map