@graphty/webgpu-graph-algorithms 0.0.0 → 0.2.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/README.md +344 -23
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +32 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunks/context-E6iKaeuJ.js +3136 -0
- package/dist/chunks/context-E6iKaeuJ.js.map +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +131 -0
- package/dist/node.js.map +1 -0
- package/dist/src/accelerator.d.ts +26 -0
- package/dist/src/accelerator.d.ts.map +1 -0
- package/dist/src/accelerator.js +101 -0
- package/dist/src/accelerator.js.map +1 -0
- package/dist/src/algorithms/degree.d.ts +35 -0
- package/dist/src/algorithms/degree.d.ts.map +1 -0
- package/dist/src/algorithms/degree.js +119 -0
- package/dist/src/algorithms/degree.js.map +1 -0
- package/dist/src/browser/index.d.ts +23 -0
- package/dist/src/browser/index.d.ts.map +1 -0
- package/dist/src/browser/index.js +48 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/constants.d.ts +92 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +92 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/context.d.ts +84 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +304 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/device/acquire.d.ts +57 -0
- package/dist/src/device/acquire.d.ts.map +1 -0
- package/dist/src/device/acquire.js +232 -0
- package/dist/src/device/acquire.js.map +1 -0
- package/dist/src/device/caps.d.ts +43 -0
- package/dist/src/device/caps.d.ts.map +1 -0
- package/dist/src/device/caps.js +104 -0
- package/dist/src/device/caps.js.map +1 -0
- package/dist/src/device/error-scope.d.ts +75 -0
- package/dist/src/device/error-scope.d.ts.map +1 -0
- package/dist/src/device/error-scope.js +152 -0
- package/dist/src/device/error-scope.js.map +1 -0
- package/dist/src/device/lost.d.ts +51 -0
- package/dist/src/device/lost.d.ts.map +1 -0
- package/dist/src/device/lost.js +130 -0
- package/dist/src/device/lost.js.map +1 -0
- package/dist/src/device/webgpu-constants.d.ts +31 -0
- package/dist/src/device/webgpu-constants.d.ts.map +1 -0
- package/dist/src/device/webgpu-constants.js +31 -0
- package/dist/src/device/webgpu-constants.js.map +1 -0
- package/dist/src/errors.d.ts +56 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +57 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +27 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/kernel/batch.d.ts +116 -0
- package/dist/src/kernel/batch.d.ts.map +1 -0
- package/dist/src/kernel/batch.js +335 -0
- package/dist/src/kernel/batch.js.map +1 -0
- package/dist/src/kernel/dispatch.d.ts +59 -0
- package/dist/src/kernel/dispatch.d.ts.map +1 -0
- package/dist/src/kernel/dispatch.js +139 -0
- package/dist/src/kernel/dispatch.js.map +1 -0
- package/dist/src/kernel/kernel.d.ts +84 -0
- package/dist/src/kernel/kernel.d.ts.map +1 -0
- package/dist/src/kernel/kernel.js +239 -0
- package/dist/src/kernel/kernel.js.map +1 -0
- package/dist/src/kernel/pipeline-cache.d.ts +90 -0
- package/dist/src/kernel/pipeline-cache.d.ts.map +1 -0
- package/dist/src/kernel/pipeline-cache.js +251 -0
- package/dist/src/kernel/pipeline-cache.js.map +1 -0
- package/dist/src/kernel/prelude.d.ts +35 -0
- package/dist/src/kernel/prelude.d.ts.map +1 -0
- package/dist/src/kernel/prelude.js +211 -0
- package/dist/src/kernel/prelude.js.map +1 -0
- package/dist/src/kernel/profiler.d.ts +64 -0
- package/dist/src/kernel/profiler.d.ts.map +1 -0
- package/dist/src/kernel/profiler.js +120 -0
- package/dist/src/kernel/profiler.js.map +1 -0
- package/dist/src/kernel/struct-block.d.ts +122 -0
- package/dist/src/kernel/struct-block.d.ts.map +1 -0
- package/dist/src/kernel/struct-block.js +353 -0
- package/dist/src/kernel/struct-block.js.map +1 -0
- package/dist/src/kernel/uniform-ring.d.ts +70 -0
- package/dist/src/kernel/uniform-ring.d.ts.map +1 -0
- package/dist/src/kernel/uniform-ring.js +146 -0
- package/dist/src/kernel/uniform-ring.js.map +1 -0
- package/dist/src/kernel/wgsl.d.ts +88 -0
- package/dist/src/kernel/wgsl.d.ts.map +1 -0
- package/dist/src/kernel/wgsl.js +390 -0
- package/dist/src/kernel/wgsl.js.map +1 -0
- package/dist/src/kernels.d.ts +81 -0
- package/dist/src/kernels.d.ts.map +1 -0
- package/dist/src/kernels.js +417 -0
- package/dist/src/kernels.js.map +1 -0
- package/dist/src/layouts/force-simulation.d.ts +498 -0
- package/dist/src/layouts/force-simulation.d.ts.map +1 -0
- package/dist/src/layouts/force-simulation.js +1650 -0
- package/dist/src/layouts/force-simulation.js.map +1 -0
- package/dist/src/layouts/forceatlas2.d.ts +210 -0
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -0
- package/dist/src/layouts/forceatlas2.js +759 -0
- package/dist/src/layouts/forceatlas2.js.map +1 -0
- package/dist/src/layouts/inputs.d.ts +40 -0
- package/dist/src/layouts/inputs.d.ts.map +1 -0
- package/dist/src/layouts/inputs.js +185 -0
- package/dist/src/layouts/inputs.js.map +1 -0
- package/dist/src/layouts/repulsion-exact.d.ts +85 -0
- package/dist/src/layouts/repulsion-exact.d.ts.map +1 -0
- package/dist/src/layouts/repulsion-exact.js +134 -0
- package/dist/src/layouts/repulsion-exact.js.map +1 -0
- package/dist/src/layouts/seed.d.ts +56 -0
- package/dist/src/layouts/seed.d.ts.map +1 -0
- package/dist/src/layouts/seed.js +173 -0
- package/dist/src/layouts/seed.js.map +1 -0
- package/dist/src/memory/buffer-pool.d.ts +73 -0
- package/dist/src/memory/buffer-pool.d.ts.map +1 -0
- package/dist/src/memory/buffer-pool.js +170 -0
- package/dist/src/memory/buffer-pool.js.map +1 -0
- package/dist/src/memory/lease.d.ts +53 -0
- package/dist/src/memory/lease.d.ts.map +1 -0
- package/dist/src/memory/lease.js +85 -0
- package/dist/src/memory/lease.js.map +1 -0
- package/dist/src/memory/readback.d.ts +143 -0
- package/dist/src/memory/readback.d.ts.map +1 -0
- package/dist/src/memory/readback.js +375 -0
- package/dist/src/memory/readback.js.map +1 -0
- package/dist/src/memory/residency.d.ts +83 -0
- package/dist/src/memory/residency.d.ts.map +1 -0
- package/dist/src/memory/residency.js +573 -0
- package/dist/src/memory/residency.js.map +1 -0
- package/dist/src/memory/upload-plan.d.ts +101 -0
- package/dist/src/memory/upload-plan.d.ts.map +1 -0
- package/dist/src/memory/upload-plan.js +265 -0
- package/dist/src/memory/upload-plan.js.map +1 -0
- package/dist/src/node/index.d.ts +64 -0
- package/dist/src/node/index.d.ts.map +1 -0
- package/dist/src/node/index.js +183 -0
- package/dist/src/node/index.js.map +1 -0
- package/dist/src/primitives/reduce.d.ts +57 -0
- package/dist/src/primitives/reduce.d.ts.map +1 -0
- package/dist/src/primitives/reduce.js +161 -0
- package/dist/src/primitives/reduce.js.map +1 -0
- package/dist/src/primitives/segmented-reduce.d.ts +38 -0
- package/dist/src/primitives/segmented-reduce.d.ts.map +1 -0
- package/dist/src/primitives/segmented-reduce.js +211 -0
- package/dist/src/primitives/segmented-reduce.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +209 -0
- package/dist/src/types/accelerator.d.ts.map +1 -0
- package/dist/src/types/accelerator.js +8 -0
- package/dist/src/types/accelerator.js.map +1 -0
- package/dist/src/types/context.d.ts +114 -0
- package/dist/src/types/context.d.ts.map +1 -0
- package/dist/src/types/context.js +7 -0
- package/dist/src/types/context.js.map +1 -0
- package/dist/src/types/layout.d.ts +95 -0
- package/dist/src/types/layout.d.ts.map +1 -0
- package/dist/src/types/layout.js +6 -0
- package/dist/src/types/layout.js.map +1 -0
- package/dist/src/types/memory.d.ts +22 -0
- package/dist/src/types/memory.d.ts.map +1 -0
- package/dist/src/types/memory.js +7 -0
- package/dist/src/types/memory.js.map +1 -0
- package/dist/src/types/options.d.ts +78 -0
- package/dist/src/types/options.d.ts.map +1 -0
- package/dist/src/types/options.js +7 -0
- package/dist/src/types/options.js.map +1 -0
- package/dist/src/types/run.d.ts +13 -0
- package/dist/src/types/run.d.ts.map +1 -0
- package/dist/src/types/run.js +6 -0
- package/dist/src/types/run.js.map +1 -0
- package/dist/src/wgsl/degree.wgsl.d.ts +10 -0
- package/dist/src/wgsl/degree.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/degree.wgsl.js +25 -0
- package/dist/src/wgsl/degree.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +12 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.js +37 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts +13 -0
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-integrate.wgsl.js +69 -0
- package/dist/src/wgsl/fa2-integrate.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +12 -0
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +79 -0
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-speed-finalize.wgsl.d.ts +15 -0
- package/dist/src/wgsl/fa2-speed-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-speed-finalize.wgsl.js +54 -0
- package/dist/src/wgsl/fa2-speed-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +14 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +57 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-to-scene.wgsl.d.ts +11 -0
- package/dist/src/wgsl/fa2-to-scene.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fa2-to-scene.wgsl.js +19 -0
- package/dist/src/wgsl/fa2-to-scene.wgsl.js.map +1 -0
- package/dist/src/wgsl/fill.wgsl.d.ts +7 -0
- package/dist/src/wgsl/fill.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/fill.wgsl.js +14 -0
- package/dist/src/wgsl/fill.wgsl.js.map +1 -0
- package/dist/src/wgsl/reduce.wgsl.d.ts +10 -0
- package/dist/src/wgsl/reduce.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/reduce.wgsl.js +63 -0
- package/dist/src/wgsl/reduce.wgsl.js.map +1 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +13 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.js +35 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/webgpu-graph-algorithms.d.ts +1 -0
- package/dist/webgpu-graph-algorithms.js +4454 -0
- package/dist/webgpu-graph-algorithms.js.map +1 -0
- package/package.json +108 -17
- package/src/accelerator.ts +117 -0
- package/src/algorithms/degree.ts +142 -0
- package/src/browser/index.ts +57 -0
- package/src/constants.ts +116 -0
- package/src/context.ts +399 -0
- package/src/device/acquire.ts +256 -0
- package/src/device/caps.ts +122 -0
- package/src/device/error-scope.ts +171 -0
- package/src/device/lost.ts +142 -0
- package/src/device/webgpu-constants.ts +44 -0
- package/src/errors.ts +94 -0
- package/src/index.ts +102 -0
- package/src/kernel/batch.ts +427 -0
- package/src/kernel/dispatch.ts +162 -0
- package/src/kernel/kernel.ts +311 -0
- package/src/kernel/pipeline-cache.ts +288 -0
- package/src/kernel/prelude.ts +229 -0
- package/src/kernel/profiler.ts +148 -0
- package/src/kernel/struct-block.ts +439 -0
- package/src/kernel/uniform-ring.ts +184 -0
- package/src/kernel/wgsl.ts +490 -0
- package/src/kernels.ts +511 -0
- package/src/layouts/force-simulation.ts +2111 -0
- package/src/layouts/forceatlas2.ts +942 -0
- package/src/layouts/inputs.ts +252 -0
- package/src/layouts/repulsion-exact.ts +183 -0
- package/src/layouts/seed.ts +198 -0
- package/src/memory/buffer-pool.ts +204 -0
- package/src/memory/lease.ts +93 -0
- package/src/memory/readback.ts +429 -0
- package/src/memory/residency.ts +753 -0
- package/src/memory/upload-plan.ts +350 -0
- package/src/node/index.ts +230 -0
- package/src/primitives/reduce.ts +233 -0
- package/src/primitives/segmented-reduce.ts +270 -0
- package/src/types/accelerator.ts +236 -0
- package/src/types/context.ts +135 -0
- package/src/types/layout.ts +103 -0
- package/src/types/memory.ts +23 -0
- package/src/types/options.ts +84 -0
- package/src/types/run.ts +13 -0
- package/src/wgsl/degree.wgsl.ts +24 -0
- package/src/wgsl/fa2-attraction.wgsl.ts +37 -0
- package/src/wgsl/fa2-integrate.wgsl.ts +69 -0
- package/src/wgsl/fa2-repulsion-exact.wgsl.ts +78 -0
- package/src/wgsl/fa2-speed-finalize.wgsl.ts +53 -0
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +57 -0
- package/src/wgsl/fa2-to-scene.wgsl.ts +19 -0
- package/src/wgsl/fill.wgsl.ts +13 -0
- package/src/wgsl/reduce.wgsl.ts +62 -0
- package/src/wgsl/segmented-reduce.wgsl.ts +35 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WGSL prelude every composed module receives first (contract 4.1), the two reduction-helper blocks the
|
|
3
|
+
* composer splices for a body whose spec lists `needs: ["subgroups"]` (contract 4.3, D16), and the WGSL
|
|
4
|
+
* reserved-word list the composer rejects identifiers from (contract 3.9).
|
|
5
|
+
*
|
|
6
|
+
* CONTRACT DECISION (3.9): this text lives in the kernel layer, not in `src/wgsl/`, because the composer must own
|
|
7
|
+
* what it splices and `src/wgsl/` then holds kernel BODIES only. Every numeric constant is interpolated from
|
|
8
|
+
* `constants.ts` and graph-format's `INVALID_INDEX`; the literal-grep test of spec 3.5 (`test/kernel/wgsl.test.ts`)
|
|
9
|
+
* covers this file's template, so no u32 sentinel or workgroup-size literal is ever typed here.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { INVALID_INDEX } from "@graphty/graph-format";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
FA2_COINCIDENT_SQ,
|
|
16
|
+
FA2_DISTANCE_FLOOR,
|
|
17
|
+
FA2_DISTANCE_FLOOR_SQ,
|
|
18
|
+
FA2_FLAG_FIRST,
|
|
19
|
+
MAX_WORKGROUPS_PER_DIM,
|
|
20
|
+
U32_MAX,
|
|
21
|
+
WORKGROUP_SIZE,
|
|
22
|
+
} from "../constants.js";
|
|
23
|
+
import { WebGpuGraphError } from "../errors.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Formats a finite number as a WGSL f32 literal that always carries a decimal point or an exponent (`0.01`,
|
|
27
|
+
* `1e-8`, `2.0`), so an integral value never becomes an abstract-int literal (contract 4.1).
|
|
28
|
+
* @param value - the number to format
|
|
29
|
+
* @returns the literal text
|
|
30
|
+
*/
|
|
31
|
+
export function wgslF32Literal(value: number): string {
|
|
32
|
+
if (!Number.isFinite(value)) {
|
|
33
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `wgslF32Literal: ${value} is not a finite number`, {
|
|
34
|
+
argument: "value",
|
|
35
|
+
value,
|
|
36
|
+
expected: "a finite number",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const text = String(value);
|
|
40
|
+
return /[.e]/.test(text) ? text : `${text}.0`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The prelude text with the constants interpolated from constants.ts and graph-format's INVALID_INDEX (4.1). */
|
|
44
|
+
export const PRELUDE_WGSL: string = /* wgsl */ `// ---- prelude: constants, standard overrides, helpers (every module receives this text first)
|
|
45
|
+
const INVALID_INDEX: u32 = ${INVALID_INDEX}u;
|
|
46
|
+
const U32_MAX: u32 = ${U32_MAX}u;
|
|
47
|
+
const MAX_WORKGROUPS_PER_DIM: u32 = ${MAX_WORKGROUPS_PER_DIM}u;
|
|
48
|
+
const FA2_DIST_FLOOR: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR)};
|
|
49
|
+
const FA2_DIST_FLOOR_SQ: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR_SQ)};
|
|
50
|
+
const FA2_COINCIDENT_SQ: f32 = ${wgslF32Literal(FA2_COINCIDENT_SQ)};
|
|
51
|
+
const FA2_FLAG_FIRST: u32 = ${FA2_FLAG_FIRST}u;
|
|
52
|
+
const F32_MAX: f32 = 0x1.fffffep+127;
|
|
53
|
+
override WG: u32 = ${WORKGROUP_SIZE}u;
|
|
54
|
+
override USE_PERM: bool = false;
|
|
55
|
+
override HAS_WEIGHTS: bool = false;
|
|
56
|
+
override SUBGROUP_MIN: u32 = 4u;
|
|
57
|
+
override SUBGROUP_MAX: u32 = 0u;
|
|
58
|
+
|
|
59
|
+
fn linear_id(wid: vec3<u32>, lid: u32) -> u32 { return (wid.x + wid.y * MAX_WORKGROUPS_PER_DIM) * WG + lid; }
|
|
60
|
+
fn group_id(wid: vec3<u32>) -> u32 { return wid.x + wid.y * MAX_WORKGROUPS_PER_DIM; }
|
|
61
|
+
fn lowbias32(x0: u32) -> u32 {
|
|
62
|
+
var x = x0;
|
|
63
|
+
x = x ^ (x >> 16u);
|
|
64
|
+
x = x * 0x7feb352du;
|
|
65
|
+
x = x ^ (x >> 15u);
|
|
66
|
+
x = x * 0x846ca68bu;
|
|
67
|
+
x = x ^ (x >> 16u);
|
|
68
|
+
return x;
|
|
69
|
+
}
|
|
70
|
+
fn mask_bit(w: u32, i: u32) -> bool { return ((w >> (i & 31u)) & 1u) == 1u; }
|
|
71
|
+
fn unpack_u8(w: u32, i: u32) -> u32 { return (w >> (8u * (i & 3u))) & 0xFFu; }
|
|
72
|
+
fn pair_hash(i: u32, j: u32) -> u32 { return lowbias32((min(i, j) * 0x9E3779B9u) ^ max(i, j)); }
|
|
73
|
+
fn hash_unit(h: u32) -> f32 { return f32(h >> 8u) * (1.0 / 16777216.0); }
|
|
74
|
+
fn hash_dir(h: u32, dim: u32) -> vec3f {
|
|
75
|
+
let phi = 6.283185307179586 * hash_unit(h);
|
|
76
|
+
if (dim == 2u) { return vec3f(cos(phi), sin(phi), 0.0); }
|
|
77
|
+
let z = 2.0 * hash_unit(lowbias32(h ^ 0x5bd1e995u)) - 1.0;
|
|
78
|
+
let r = sqrt(max(0.0, 1.0 - z * z));
|
|
79
|
+
return vec3f(r * cos(phi), r * sin(phi), z);
|
|
80
|
+
}
|
|
81
|
+
fn kick_dir(i: u32, j: u32, dim: u32) -> vec3f {
|
|
82
|
+
let d = hash_dir(pair_hash(i, j), dim);
|
|
83
|
+
return select(d, -d, i > j);
|
|
84
|
+
}`;
|
|
85
|
+
|
|
86
|
+
/** Line count of PRELUDE_WGSL (the compilation-info formatter subtracts it plus the emitted declarations). */
|
|
87
|
+
export const PRELUDE_LINES: number = PRELUDE_WGSL.split("\n").length;
|
|
88
|
+
|
|
89
|
+
/** The workgroup-memory reduction helpers (the twin) (4.3). */
|
|
90
|
+
export const REDUCE_HELPERS_WORKGROUP_WGSL: string = /* wgsl */ `var<workgroup> wg_scratch_v: array<vec4f, WG>;
|
|
91
|
+
var<workgroup> wg_scratch_u: array<u32, WG>;
|
|
92
|
+
fn combine_v(a: vec4f, b: vec4f, op: u32) -> vec4f {
|
|
93
|
+
if (op == 1u) { return min(a, b); }
|
|
94
|
+
if (op == 2u) { return max(a, b); }
|
|
95
|
+
return a + b;
|
|
96
|
+
}
|
|
97
|
+
fn combine_u(a: u32, b: u32, op: u32) -> u32 {
|
|
98
|
+
if (op == 1u) { return min(a, b); }
|
|
99
|
+
if (op == 2u) { return max(a, b); }
|
|
100
|
+
return a + b;
|
|
101
|
+
}
|
|
102
|
+
fn wg_reduce_vec4(v: vec4f, lid: u32, op: u32) -> vec4f {
|
|
103
|
+
workgroupBarrier();
|
|
104
|
+
wg_scratch_v[lid] = v;
|
|
105
|
+
workgroupBarrier();
|
|
106
|
+
for (var s = WG / 2u; s > 0u; s = s >> 1u) {
|
|
107
|
+
if (lid < s) { wg_scratch_v[lid] = combine_v(wg_scratch_v[lid], wg_scratch_v[lid + s], op); }
|
|
108
|
+
workgroupBarrier();
|
|
109
|
+
}
|
|
110
|
+
let total = wg_scratch_v[0];
|
|
111
|
+
workgroupBarrier();
|
|
112
|
+
return total;
|
|
113
|
+
}
|
|
114
|
+
fn wg_reduce_u32(v: u32, lid: u32, op: u32) -> u32 {
|
|
115
|
+
workgroupBarrier();
|
|
116
|
+
wg_scratch_u[lid] = v;
|
|
117
|
+
workgroupBarrier();
|
|
118
|
+
for (var s = WG / 2u; s > 0u; s = s >> 1u) {
|
|
119
|
+
if (lid < s) { wg_scratch_u[lid] = combine_u(wg_scratch_u[lid], wg_scratch_u[lid + s], op); }
|
|
120
|
+
workgroupBarrier();
|
|
121
|
+
}
|
|
122
|
+
let total = wg_scratch_u[0];
|
|
123
|
+
workgroupBarrier();
|
|
124
|
+
return total;
|
|
125
|
+
}
|
|
126
|
+
fn wg_reduce_f32(v: f32, lid: u32, op: u32) -> f32 { return wg_reduce_vec4(vec4f(v, 0.0, 0.0, 0.0), lid, op).x; }`;
|
|
127
|
+
|
|
128
|
+
/** The subgroup reduction helpers, spliced only with `enable subgroups;` (4.3). */
|
|
129
|
+
export const REDUCE_HELPERS_SUBGROUP_WGSL: string = /* wgsl */ `override SG_SLOTS: u32 = (WG + SUBGROUP_MIN - 1u) / SUBGROUP_MIN; // one slot per subgroup; the count is largest when the compiler picks the SMALLEST size
|
|
130
|
+
var<workgroup> sg_counter: atomic<u32>;
|
|
131
|
+
var<workgroup> sg_val_v: array<vec4f, SG_SLOTS>;
|
|
132
|
+
var<workgroup> sg_val_u: array<u32, SG_SLOTS>;
|
|
133
|
+
var<workgroup> sg_key: array<u32, SG_SLOTS>;
|
|
134
|
+
var<workgroup> sg_sorted_v: array<vec4f, SG_SLOTS>;
|
|
135
|
+
var<workgroup> sg_sorted_u: array<u32, SG_SLOTS>;
|
|
136
|
+
fn combine_v(a: vec4f, b: vec4f, op: u32) -> vec4f {
|
|
137
|
+
if (op == 1u) { return min(a, b); }
|
|
138
|
+
if (op == 2u) { return max(a, b); }
|
|
139
|
+
return a + b;
|
|
140
|
+
}
|
|
141
|
+
fn combine_u(a: u32, b: u32, op: u32) -> u32 {
|
|
142
|
+
if (op == 1u) { return min(a, b); }
|
|
143
|
+
if (op == 2u) { return max(a, b); }
|
|
144
|
+
return a + b;
|
|
145
|
+
}
|
|
146
|
+
fn wg_reduce_vec4(v: vec4f, lid: u32, op: u32) -> vec4f {
|
|
147
|
+
workgroupBarrier();
|
|
148
|
+
if (lid == 0u) { atomicStore(&sg_counter, 0u); }
|
|
149
|
+
workgroupBarrier();
|
|
150
|
+
let s_add = subgroupAdd(v);
|
|
151
|
+
let s_min = subgroupMin(v);
|
|
152
|
+
let s_max = subgroupMax(v);
|
|
153
|
+
var partial = s_add;
|
|
154
|
+
if (op == 1u) { partial = s_min; }
|
|
155
|
+
if (op == 2u) { partial = s_max; }
|
|
156
|
+
let key = subgroupMin(lid); // the smallest local id of this subgroup: a stable identity without @builtin(subgroup_id)
|
|
157
|
+
var slot = 0u;
|
|
158
|
+
if (subgroupElect()) { slot = atomicAdd(&sg_counter, 1u); } // D16: the elected lane takes a slot from the counter
|
|
159
|
+
slot = subgroupBroadcast(slot, 0u); // and broadcasts it (lane 0 is the elected lane in uniform control flow)
|
|
160
|
+
if (subgroupElect()) { sg_val_v[slot] = partial; sg_key[slot] = key; }
|
|
161
|
+
workgroupBarrier();
|
|
162
|
+
let count = atomicLoad(&sg_counter);
|
|
163
|
+
if (lid < count) { // rank the slots by key so the final sum has a fixed order (11.9 item 4)
|
|
164
|
+
let mine = sg_key[lid];
|
|
165
|
+
var rank = 0u;
|
|
166
|
+
for (var k = 0u; k < count; k = k + 1u) { if (sg_key[k] < mine) { rank = rank + 1u; } }
|
|
167
|
+
sg_sorted_v[rank] = sg_val_v[lid];
|
|
168
|
+
}
|
|
169
|
+
workgroupBarrier();
|
|
170
|
+
var total = sg_sorted_v[0];
|
|
171
|
+
for (var k = 1u; k < count; k = k + 1u) { total = combine_v(total, sg_sorted_v[k], op); }
|
|
172
|
+
workgroupBarrier();
|
|
173
|
+
return total;
|
|
174
|
+
}
|
|
175
|
+
fn wg_reduce_u32(v: u32, lid: u32, op: u32) -> u32 {
|
|
176
|
+
workgroupBarrier();
|
|
177
|
+
if (lid == 0u) { atomicStore(&sg_counter, 0u); }
|
|
178
|
+
workgroupBarrier();
|
|
179
|
+
let s_add = subgroupAdd(v);
|
|
180
|
+
let s_min = subgroupMin(v);
|
|
181
|
+
let s_max = subgroupMax(v);
|
|
182
|
+
var partial = s_add;
|
|
183
|
+
if (op == 1u) { partial = s_min; }
|
|
184
|
+
if (op == 2u) { partial = s_max; }
|
|
185
|
+
let key = subgroupMin(lid);
|
|
186
|
+
var slot = 0u;
|
|
187
|
+
if (subgroupElect()) { slot = atomicAdd(&sg_counter, 1u); }
|
|
188
|
+
slot = subgroupBroadcast(slot, 0u);
|
|
189
|
+
if (subgroupElect()) { sg_val_u[slot] = partial; sg_key[slot] = key; }
|
|
190
|
+
workgroupBarrier();
|
|
191
|
+
let count = atomicLoad(&sg_counter);
|
|
192
|
+
if (lid < count) {
|
|
193
|
+
let mine = sg_key[lid];
|
|
194
|
+
var rank = 0u;
|
|
195
|
+
for (var k = 0u; k < count; k = k + 1u) { if (sg_key[k] < mine) { rank = rank + 1u; } }
|
|
196
|
+
sg_sorted_u[rank] = sg_val_u[lid];
|
|
197
|
+
}
|
|
198
|
+
workgroupBarrier();
|
|
199
|
+
var total = sg_sorted_u[0];
|
|
200
|
+
for (var k = 1u; k < count; k = k + 1u) { total = combine_u(total, sg_sorted_u[k], op); }
|
|
201
|
+
workgroupBarrier();
|
|
202
|
+
return total;
|
|
203
|
+
}
|
|
204
|
+
fn wg_reduce_f32(v: f32, lid: u32, op: u32) -> f32 { return wg_reduce_vec4(vec4f(v, 0.0, 0.0, 0.0), lid, op).x; }`;
|
|
205
|
+
|
|
206
|
+
/** The helper function names a body may call when its spec lists needs: ["subgroups"]. */
|
|
207
|
+
export const REDUCE_HELPER_NAMES = ["wg_reduce_f32", "wg_reduce_u32", "wg_reduce_vec4"] as const;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The WGSL reserved words of spec section 16.2 (the `_reserved` production), frozen; composeWgsl rejects a body or
|
|
211
|
+
* snippet that uses one (3.9). PLAN DECISION: the list is the 146 words of the W3C Candidate Recommendation Draft
|
|
212
|
+
* of 2026-08-31 (identical to the editor's draft of 2026-09-01); the contract's count of 147 predates the removal
|
|
213
|
+
* of `binding_array`. `free`, `valid`, `tile`, `slot`, `key` and `count` are NOT reserved.
|
|
214
|
+
*/
|
|
215
|
+
export const WGSL_RESERVED_WORDS: readonly string[] = Object.freeze(
|
|
216
|
+
`NULL Self abstract active alignas alignof as asm asm_fragment async attribute auto await become cast catch class
|
|
217
|
+
co_await co_return co_yield coherent column_major common compile compile_fragment concept const_cast consteval
|
|
218
|
+
constexpr constinit crate debugger decltype delete demote demote_to_helper do dynamic_cast enum explicit export
|
|
219
|
+
extends extern external fallthrough filter final finally friend from fxgroup get goto groupshared highp impl
|
|
220
|
+
implements import inline instanceof interface layout lowp macro macro_rules match mediump meta mod module move
|
|
221
|
+
mut mutable namespace new nil noexcept noinline nointerpolation non_coherent noncoherent noperspective null
|
|
222
|
+
nullptr of operator package packoffset partition pass patch pixelfragment precise precision premerge priv
|
|
223
|
+
protected pub public readonly ref regardless register reinterpret_cast require resource restrict self set shared
|
|
224
|
+
sizeof smooth snorm static static_assert static_cast std subroutine super target template this thread_local
|
|
225
|
+
throw trait try type typedef typeid typename typeof union unless unorm unsafe unsized use using varying virtual
|
|
226
|
+
volatile wgsl where with writeonly yield`
|
|
227
|
+
.trim()
|
|
228
|
+
.split(/\s+/),
|
|
229
|
+
);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timestamp profiling of spec 5.5: when "timestamp-query" was granted the context owns a Profiler
|
|
3
|
+
* whose query set (PROFILER_QUERY_SLOTS slots, two per pass) is written by the timestampWrites of
|
|
4
|
+
* every CommandBatch pass; `resolveInto(batch)` records the resolve plus a copy into the batch's
|
|
5
|
+
* staging slot and `timings(bytes, request)` decodes `{ label, ns }` per pass from the batch's
|
|
6
|
+
* readback bytes. Chromium quantises timestamps to 100 us (`quantised: true`); Dawn-node reports
|
|
7
|
+
* 1,024 ns ticks (spec 2.6). The profiler never throws: a full query set drops timings and a device
|
|
8
|
+
* without the feature disables it. Slot pairs are handed out in order and the cursor restarts at 0
|
|
9
|
+
* after every resolve: queue order guarantees the resolve of batch A reads its slots before batch B
|
|
10
|
+
* overwrites them, and one resolve buffer suffices for the same reason.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { PROFILER_QUERY_SLOTS } from "../constants.js";
|
|
14
|
+
import { BufferUsage } from "../device/webgpu-constants.js";
|
|
15
|
+
import type { CommandBatch, ReadbackRequest } from "./batch.js";
|
|
16
|
+
|
|
17
|
+
/** One resolved pass timing. */
|
|
18
|
+
export interface PassTiming {
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly ns: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A pass whose begin / end slots were handed out and not yet resolved. */
|
|
24
|
+
interface PendingPass {
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly begin: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Timestamp profiling when "timestamp-query" was granted (spec 5.5). */
|
|
30
|
+
export class Profiler {
|
|
31
|
+
/** True when the feature was granted and a query set exists. */
|
|
32
|
+
readonly enabled: boolean;
|
|
33
|
+
/** True in browsers (100 us quantisation), false under Dawn-node (spec 2.6). */
|
|
34
|
+
readonly quantised: boolean;
|
|
35
|
+
private readonly slots: number;
|
|
36
|
+
private readonly querySet: GPUQuerySet | null;
|
|
37
|
+
private readonly resolveBuffer: GPUBuffer | null;
|
|
38
|
+
private readonly labels = new WeakMap<ReadbackRequest, readonly string[]>();
|
|
39
|
+
private pending: PendingPass[] = [];
|
|
40
|
+
private cursor = 0;
|
|
41
|
+
private destroyed = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates the query set and the resolve buffer when usable; `slots` defaults to PROFILER_QUERY_SLOTS = 256
|
|
45
|
+
* (spec 5.5); 2 query slots per pass. `enabled` is also false when the device lacks "timestamp-query" or
|
|
46
|
+
* `slots < 2`, so no caller can make the profiler raise a validation error.
|
|
47
|
+
* @param device - the device the query set is created on
|
|
48
|
+
* @param enabled - whether the caller wants profiling (the context passes `features.has("timestamp-query")`)
|
|
49
|
+
* @param quantised - true in browsers
|
|
50
|
+
* @param slots - query slots, two per pass (default PROFILER_QUERY_SLOTS)
|
|
51
|
+
*/
|
|
52
|
+
constructor(device: GPUDevice, enabled: boolean, quantised: boolean, slots?: number) {
|
|
53
|
+
const count = slots ?? PROFILER_QUERY_SLOTS;
|
|
54
|
+
this.quantised = quantised;
|
|
55
|
+
this.slots = count;
|
|
56
|
+
const usable = enabled && count >= 2 && device.features.has("timestamp-query");
|
|
57
|
+
this.enabled = usable;
|
|
58
|
+
if (usable) {
|
|
59
|
+
this.querySet = device.createQuerySet({ label: "profiler/timestamps", type: "timestamp", count });
|
|
60
|
+
this.resolveBuffer = device.createBuffer({
|
|
61
|
+
label: "profiler/resolve",
|
|
62
|
+
size: count * 8,
|
|
63
|
+
usage: BufferUsage.QUERY_RESOLVE | BufferUsage.COPY_SRC,
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
this.querySet = null;
|
|
67
|
+
this.resolveBuffer = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The timestampWrites descriptor for a new pass, or undefined when disabled / out of slots.
|
|
73
|
+
* @param label - the pass name reported by `timings()`
|
|
74
|
+
* @returns the descriptor a CommandBatch puts on beginComputePass, or undefined
|
|
75
|
+
*/
|
|
76
|
+
beginPass(label: string): GPUComputePassTimestampWrites | undefined {
|
|
77
|
+
if (this.querySet === null || this.destroyed || this.cursor + 2 > this.slots) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const begin = this.cursor;
|
|
81
|
+
this.cursor += 2;
|
|
82
|
+
this.pending.push({ label, begin });
|
|
83
|
+
return { querySet: this.querySet, beginningOfPassWriteIndex: begin, endOfPassWriteIndex: begin + 1 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Records the resolve + copy into the batch's staging slot; returns the request's byte range, or null when
|
|
88
|
+
* nothing was written (disabled, destroyed, or no pass begun since the last resolve).
|
|
89
|
+
* @param batch - the batch whose passes were timed (it must still be open)
|
|
90
|
+
* @returns the readback request `timings()` decodes, or null
|
|
91
|
+
*/
|
|
92
|
+
resolveInto(batch: CommandBatch): ReadbackRequest | null {
|
|
93
|
+
if (this.querySet === null || this.resolveBuffer === null || this.destroyed || this.pending.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const count = this.cursor;
|
|
97
|
+
batch.resolveQuerySet(this.querySet, 0, count, this.resolveBuffer);
|
|
98
|
+
const request = batch.readback(this.resolveBuffer, 0, count * 8);
|
|
99
|
+
this.labels.set(
|
|
100
|
+
request,
|
|
101
|
+
this.pending.map((pass) => pass.label),
|
|
102
|
+
);
|
|
103
|
+
this.pending = [];
|
|
104
|
+
this.cursor = 0;
|
|
105
|
+
return request;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Decodes the timings of a batch from its readback bytes.
|
|
110
|
+
* @param bytes - the resolved readback of the batch
|
|
111
|
+
* @param request - the request `resolveInto()` returned for it
|
|
112
|
+
* @returns one timing per pass in pass order (empty for a request this profiler did not create)
|
|
113
|
+
*/
|
|
114
|
+
timings(bytes: ArrayBuffer, request: ReadbackRequest): readonly PassTiming[] {
|
|
115
|
+
const labels = this.labels.get(request);
|
|
116
|
+
if (labels === undefined) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const view = new DataView(bytes);
|
|
120
|
+
const out: PassTiming[] = [];
|
|
121
|
+
for (let i = 0; i < labels.length; i++) {
|
|
122
|
+
const at = request.offset + i * 16;
|
|
123
|
+
if (at + 16 > bytes.byteLength) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
const begin = view.getBigUint64(at, true);
|
|
127
|
+
const end = view.getBigUint64(at + 8, true);
|
|
128
|
+
out.push({ label: labels[i], ns: Number(end - begin) });
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Destroys the query set and the resolve buffer; idempotent. */
|
|
134
|
+
destroy(): void {
|
|
135
|
+
if (this.destroyed) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.destroyed = true;
|
|
139
|
+
this.pending = [];
|
|
140
|
+
this.cursor = 0;
|
|
141
|
+
if (this.querySet !== null) {
|
|
142
|
+
this.querySet.destroy();
|
|
143
|
+
}
|
|
144
|
+
if (this.resolveBuffer !== null) {
|
|
145
|
+
this.resolveBuffer.destroy();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|