@graphty/webgpu-graph-algorithms 0.5.0 → 0.6.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 +104 -52
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-CRbw2Wyo.js → context-BXqgCifx.js} +225 -33
- package/dist/chunks/context-BXqgCifx.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +12 -10
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +32 -10
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/algorithms/components.d.ts.map +1 -1
- package/dist/src/algorithms/components.js +12 -13
- package/dist/src/algorithms/components.js.map +1 -1
- package/dist/src/algorithms/degree.d.ts +6 -8
- package/dist/src/algorithms/degree.d.ts.map +1 -1
- package/dist/src/algorithms/degree.js +58 -35
- package/dist/src/algorithms/degree.js.map +1 -1
- package/dist/src/algorithms/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/pagerank.js +16 -14
- package/dist/src/algorithms/pagerank.js.map +1 -1
- package/dist/src/algorithms/power-iteration.d.ts +2 -2
- package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
- package/dist/src/algorithms/power-iteration.js +17 -14
- package/dist/src/algorithms/power-iteration.js.map +1 -1
- package/dist/src/constants.d.ts +85 -8
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +85 -8
- package/dist/src/constants.js.map +1 -1
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +2 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +10 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +14 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/dispatch.d.ts +8 -3
- package/dist/src/kernel/dispatch.d.ts.map +1 -1
- package/dist/src/kernel/dispatch.js +18 -7
- package/dist/src/kernel/dispatch.js.map +1 -1
- package/dist/src/kernel/kernel.d.ts +30 -1
- package/dist/src/kernel/kernel.d.ts.map +1 -1
- package/dist/src/kernel/kernel.js +49 -5
- package/dist/src/kernel/kernel.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +9 -1
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernel/profiler.d.ts +15 -3
- package/dist/src/kernel/profiler.d.ts.map +1 -1
- package/dist/src/kernel/profiler.js +27 -4
- package/dist/src/kernel/profiler.js.map +1 -1
- package/dist/src/kernels.d.ts +18 -8
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +345 -22
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/layouts/calibrate.d.ts +51 -0
- package/dist/src/layouts/calibrate.d.ts.map +1 -0
- package/dist/src/layouts/calibrate.js +172 -0
- package/dist/src/layouts/calibrate.js.map +1 -0
- package/dist/src/layouts/force-simulation.d.ts +42 -5
- package/dist/src/layouts/force-simulation.d.ts.map +1 -1
- package/dist/src/layouts/force-simulation.js +84 -22
- package/dist/src/layouts/force-simulation.js.map +1 -1
- package/dist/src/layouts/forceatlas2.d.ts +107 -38
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
- package/dist/src/layouts/forceatlas2.js +297 -290
- package/dist/src/layouts/forceatlas2.js.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.d.ts +241 -0
- package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -0
- package/dist/src/layouts/fruchterman-reingold.js +739 -0
- package/dist/src/layouts/fruchterman-reingold.js.map +1 -0
- package/dist/src/layouts/model-common.d.ts +140 -0
- package/dist/src/layouts/model-common.d.ts.map +1 -0
- package/dist/src/layouts/model-common.js +269 -0
- package/dist/src/layouts/model-common.js.map +1 -0
- package/dist/src/layouts/repulsion-grid.d.ts +152 -0
- package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
- package/dist/src/layouts/repulsion-grid.js +318 -0
- package/dist/src/layouts/repulsion-grid.js.map +1 -0
- package/dist/src/layouts/spring-electrical.d.ts +224 -0
- package/dist/src/layouts/spring-electrical.d.ts.map +1 -0
- package/dist/src/layouts/spring-electrical.js +665 -0
- package/dist/src/layouts/spring-electrical.js.map +1 -0
- package/dist/src/memory/residency.d.ts +6 -2
- package/dist/src/memory/residency.d.ts.map +1 -1
- package/dist/src/memory/residency.js +84 -14
- package/dist/src/memory/residency.js.map +1 -1
- package/dist/src/primitives/core-shape.d.ts +38 -2
- package/dist/src/primitives/core-shape.d.ts.map +1 -1
- package/dist/src/primitives/core-shape.js +71 -3
- package/dist/src/primitives/core-shape.js.map +1 -1
- package/dist/src/primitives/grid-pyramid.d.ts +71 -0
- package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
- package/dist/src/primitives/grid-pyramid.js +143 -0
- package/dist/src/primitives/grid-pyramid.js.map +1 -0
- package/dist/src/primitives/grid.d.ts +118 -0
- package/dist/src/primitives/grid.d.ts.map +1 -0
- package/dist/src/primitives/grid.js +225 -0
- package/dist/src/primitives/grid.js.map +1 -0
- package/dist/src/primitives/histogram.d.ts +67 -0
- package/dist/src/primitives/histogram.d.ts.map +1 -0
- package/dist/src/primitives/histogram.js +190 -0
- package/dist/src/primitives/histogram.js.map +1 -0
- package/dist/src/primitives/radix-sort.d.ts +75 -0
- package/dist/src/primitives/radix-sort.d.ts.map +1 -0
- package/dist/src/primitives/radix-sort.js +168 -0
- package/dist/src/primitives/radix-sort.js.map +1 -0
- package/dist/src/primitives/scan.d.ts +44 -0
- package/dist/src/primitives/scan.d.ts.map +1 -0
- package/dist/src/primitives/scan.js +151 -0
- package/dist/src/primitives/scan.js.map +1 -0
- package/dist/src/primitives/segmented-reduce.d.ts +25 -17
- package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
- package/dist/src/primitives/segmented-reduce.js +166 -47
- package/dist/src/primitives/segmented-reduce.js.map +1 -1
- package/dist/src/primitives/spmv.d.ts +18 -14
- package/dist/src/primitives/spmv.d.ts.map +1 -1
- package/dist/src/primitives/spmv.js +94 -58
- package/dist/src/primitives/spmv.js.map +1 -1
- package/dist/src/primitives/verify.d.ts +49 -0
- package/dist/src/primitives/verify.d.ts.map +1 -0
- package/dist/src/primitives/verify.js +229 -0
- package/dist/src/primitives/verify.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +7 -3
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/context.d.ts +53 -0
- package/dist/src/types/context.d.ts.map +1 -1
- package/dist/src/types/layout.d.ts +52 -0
- package/dist/src/types/layout.d.ts.map +1 -1
- package/dist/src/types/options.d.ts +43 -1
- package/dist/src/types/options.d.ts.map +1 -1
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -8
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js +100 -17
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts +7 -2
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-integrate.wgsl.js +28 -2
- package/dist/src/wgsl/fa2-integrate.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +4 -2
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +14 -5
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +12 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +54 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.js +15 -0
- package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
- package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.js +14 -0
- package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.js +30 -0
- package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
- package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
- package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webgpu-graph-algorithms.js +5016 -1130
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +10 -7
- package/src/accelerator.ts +46 -12
- package/src/algorithms/components.ts +12 -16
- package/src/algorithms/degree.ts +58 -43
- package/src/algorithms/pagerank.ts +20 -18
- package/src/algorithms/power-iteration.ts +19 -18
- package/src/constants.ts +108 -8
- package/src/errors.ts +3 -1
- package/src/index.ts +25 -5
- package/src/kernel/dispatch.ts +18 -7
- package/src/kernel/kernel.ts +59 -5
- package/src/kernel/prelude.ts +15 -0
- package/src/kernel/profiler.ts +28 -4
- package/src/kernels.ts +378 -24
- package/src/layouts/calibrate.ts +187 -0
- package/src/layouts/force-simulation.ts +111 -26
- package/src/layouts/forceatlas2.ts +346 -324
- package/src/layouts/fruchterman-reingold.ts +918 -0
- package/src/layouts/model-common.ts +323 -0
- package/src/layouts/repulsion-grid.ts +451 -0
- package/src/layouts/spring-electrical.ts +845 -0
- package/src/memory/residency.ts +126 -20
- package/src/primitives/core-shape.ts +91 -4
- package/src/primitives/grid-pyramid.ts +221 -0
- package/src/primitives/grid.ts +349 -0
- package/src/primitives/histogram.ts +273 -0
- package/src/primitives/radix-sort.ts +246 -0
- package/src/primitives/scan.ts +197 -0
- package/src/primitives/segmented-reduce.ts +214 -56
- package/src/primitives/spmv.ts +125 -65
- package/src/primitives/verify.ts +249 -0
- package/src/types/accelerator.ts +15 -3
- package/src/types/context.ts +56 -0
- package/src/types/layout.ts +58 -0
- package/src/types/options.ts +45 -1
- package/src/wgsl/counting-scatter.wgsl.ts +16 -0
- package/src/wgsl/fa2-attraction.wgsl.ts +100 -17
- package/src/wgsl/fa2-integrate.wgsl.ts +28 -2
- package/src/wgsl/fa2-repulsion-exact.wgsl.ts +14 -5
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +54 -0
- package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
- package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
- package/src/wgsl/grid-centroid.wgsl.ts +28 -0
- package/src/wgsl/grid-downsample.wgsl.ts +27 -0
- package/src/wgsl/grid-far-field.wgsl.ts +97 -0
- package/src/wgsl/grid-near-field.wgsl.ts +128 -0
- package/src/wgsl/histogram.wgsl.ts +14 -0
- package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
- package/src/wgsl/radix-hist.wgsl.ts +30 -0
- package/src/wgsl/radix-scatter.wgsl.ts +39 -0
- package/src/wgsl/scan-add.wgsl.ts +13 -0
- package/src/wgsl/scan-block.wgsl.ts +29 -0
- package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
- package/src/wgsl/spmv-pull.wgsl.ts +110 -36
- package/dist/chunks/context-CRbw2Wyo.js.map +0 -1
package/src/constants.ts
CHANGED
|
@@ -13,6 +13,8 @@ export const MAX_WORKGROUPS_PER_DIM = 65535;
|
|
|
13
13
|
export const MAX_1D_ITEMS = MAX_WORKGROUPS_PER_DIM * WORKGROUP_SIZE;
|
|
14
14
|
/** The largest u32 (the `min` identity of the u32 reduce); interpolated into the prelude as `U32_MAX` so no body types the literal (contract 4.1). */
|
|
15
15
|
export const U32_MAX = 0xffffffff;
|
|
16
|
+
/** The bins of one radix-sort pass, 2^8 (spec 6 row 6; P4 PD-5: 8 bits per pass); interpolated into the prelude as `RADIX_BINS` and `RADIX_DIGIT_MASK` (= bins - 1) so no body types the literal. */
|
|
17
|
+
export const RADIX_BINS = 256;
|
|
16
18
|
/** Arc-window boundaries are multiples of 64 arcs = 256 bytes (design 10.6). */
|
|
17
19
|
export const ARC_WINDOW_ALIGN = 64;
|
|
18
20
|
/** Storage-binding offset alignment the package always honours (spec 2.6): the graph-format arena is 256-aligned. */
|
|
@@ -20,14 +22,30 @@ export const STORAGE_ALIGN = 256;
|
|
|
20
22
|
/** Stride of one UniformRing slot: minUniformBufferOffsetAlignment is 256 on every runtime the package targets (spec 5.3). */
|
|
21
23
|
export const UNIFORM_SLOT_BYTES = 256;
|
|
22
24
|
/**
|
|
23
|
-
* Exact-tier crossover default, re-fixed at
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* Exact-tier crossover default, re-fixed at G4 by the spec 7.8 rule in full (spec Q-6; docs/decisions/G3.md section 3
|
|
26
|
+
* for the budget clause, docs/decisions/G4.md section 7 for the re-check): the largest rung of the T-4 ladder (1k / 4k /
|
|
27
|
+
* 8k / 16k / 32k / 65k, E = 10n, 2D) with <= 4 ms per iteration AND not slower than the grid tier at the same n (the
|
|
28
|
+
* `layout-grid` 2D rows: the shared rungs 32k / 65k and the re-check rungs 1k / 4k / 8k / 16k the group runs so every
|
|
29
|
+
* exact rung has a grid row), rounded down to a power of two. Measured on the RTX 4070 SUPER under Dawn-node:
|
|
30
|
+
* benchmarks/results/nvidia-lovelace-driver580.json, session 2026-09-21T06:15:17.027Z, the "ms/iteration (profiler)"
|
|
31
|
+
* rows of layout-exact and the "grid ms/iteration (profiler) ... 2D" rows of layout-grid (the GPU time of the
|
|
32
|
+
* iteration's passes at the card's working clock), exact / grid ms per iteration: 1k 0.102 / 0.290, 4k 0.262 / 0.188,
|
|
33
|
+
* 8k 0.484 / 0.211, 16k 1.064 / 0.225, 32k 2.579 / 0.298, 65k 8.450 / 0.409. The grid tier's cost is a near-constant
|
|
34
|
+
* ~0.19-0.3 ms floor (its sort, scan and pyramid passes) up to 32k, so the exact tier wins only at 1k and the rule's
|
|
35
|
+
* answer is 1024 (32768 under the budget clause alone, the G3 value; 16384 was the P4-T14 draft's value, which only
|
|
36
|
+
* evaluated the grid clause at 32k / 65k and was not a measurement). calibrateLayout's finer probe on the same card
|
|
37
|
+
* (tmp/p4/t14-review/calibrate-defaults.log: exact 0.150 vs grid 0.190 ms at 2048) puts the true crossover between
|
|
38
|
+
* 2k and 4k; the ladder has no 2k rung, and the design's basis (cosmos switches at 4,096) is within 0.07 ms per
|
|
39
|
+
* iteration of the rule's value, so the choice among 1k / 2k / 4k is an accuracy preference (the exact tier is the
|
|
40
|
+
* oracle), not a speed one. OWNER DECISION G4-D1 (docs/decisions/G4.md section 7, rows G4-D1 / G4-F9): the constant
|
|
41
|
+
* KEEPS the G3 value 32768 while the accuracy work G4-F1 leaves is open -- G4-F1 (the grid tier's exact-vs-grid RMS
|
|
42
|
+
* on the clumpy and degenerate fixtures) closed on 2026-09-21 by the owner's asserted / printed split, G4-F2 (the
|
|
43
|
+
* unbiasedness item) the same day by its re-scope to the whole-field ratio, and the far field's accuracy on those
|
|
44
|
+
* fixtures is the follow-up -- because "auto" is the default every consumer sees and the exact tier is the accurate
|
|
45
|
+
* one; the rule's answer on the dev box (1024) is recorded, not shipped, until that work closes and the value is
|
|
46
|
+
* re-fixed. A
|
|
47
|
+
* consumer whose GPU differs (integrated, Apple, T4) passes its own value through
|
|
48
|
+
* createAccelerator(ctx, { layout: { exactMaxNodes } }); calibrateLayout(ctx) measures it.
|
|
31
49
|
*/
|
|
32
50
|
export const EXACT_MAX_NODES = 32768;
|
|
33
51
|
/** Default number of MAP_READ staging buffers in the Readback ring (spec 4.4). */
|
|
@@ -114,3 +132,85 @@ export const FA2_DISTANCE_FLOOR_SQ = 0.0001;
|
|
|
114
132
|
export const FA2_COINCIDENT_SQ = 1e-8;
|
|
115
133
|
/** Bits of Fa2Params.flags (contract 4.4). */
|
|
116
134
|
export const FA2_FLAG_FIRST = 1;
|
|
135
|
+
/** Fa2Params.flags bit: the Fruchterman-Reingold temperature is the adaptive one in the state block, not the uniform's (the `cooling: "adaptive"` option). */
|
|
136
|
+
export const FA2_FLAG_ADAPTIVE = 2;
|
|
137
|
+
/** The Fruchterman-Reingold loop's starting temperature (spec 7.20; `layout/src/simulation/fruchterman-reingold.ts:30`). */
|
|
138
|
+
export const FR_START_TEMPERATURE = 0.1;
|
|
139
|
+
/** The reheat point of spec 7.20: the temperature index a reheat restarts at, as a fraction of `iterations`. */
|
|
140
|
+
export const FR_REHEAT_FRACTION = 0.7;
|
|
141
|
+
/** Adaptive cooling (Yifan Hu 2005, section 3.2): the temperature is multiplied by this when the force energy rose, divided by it after FR_COOLING_PATIENCE consecutive falls. */
|
|
142
|
+
export const FR_COOLING_STEP = 0.9;
|
|
143
|
+
/** Adaptive cooling: consecutive iterations of falling energy before the temperature grows. */
|
|
144
|
+
export const FR_COOLING_PATIENCE = 5;
|
|
145
|
+
/** Adaptive cooling: the iteration budget when `iterations` is not given (the schedule no longer needs one; this is the cap on a run that never settles). */
|
|
146
|
+
export const FR_ADAPTIVE_MAX_ITERATIONS = 10_000;
|
|
147
|
+
/** Fruchterman-Reingold defaults (spec 7.20, 9.3; the CPU simulation's, `layout/src/simulation/fruchterman-reingold.ts:32`): `k` null = `1 / sqrt(n)`. */
|
|
148
|
+
export const FR_DEFAULTS: Readonly<{
|
|
149
|
+
k: null;
|
|
150
|
+
iterations: 50;
|
|
151
|
+
fixed: null;
|
|
152
|
+
dim: 2;
|
|
153
|
+
scale: 1;
|
|
154
|
+
settleThreshold: 0.001;
|
|
155
|
+
settleWindow: 10;
|
|
156
|
+
iterationsPerStep: 1;
|
|
157
|
+
maxInFlight: 2;
|
|
158
|
+
cooling: "linear";
|
|
159
|
+
}> = Object.freeze({
|
|
160
|
+
k: null,
|
|
161
|
+
iterations: 50,
|
|
162
|
+
fixed: null,
|
|
163
|
+
dim: 2,
|
|
164
|
+
scale: 1,
|
|
165
|
+
settleThreshold: 0.001,
|
|
166
|
+
settleWindow: 10,
|
|
167
|
+
iterationsPerStep: 1,
|
|
168
|
+
maxInFlight: 2,
|
|
169
|
+
cooling: "linear",
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Spring-electrical size scaling: when `gravity` or `springCoefficient` is left to its default, ngraph's constant is
|
|
173
|
+
* multiplied by min(1, SE_SCALE_REFERENCE_NODES / n). ngraph's values were tuned for graphs of a few hundred nodes;
|
|
174
|
+
* on tens of thousands the per-node forces are so large that every node moves at the unit speed clamp and the layout
|
|
175
|
+
* never comes to rest (measured on the 58k-node Brightkite graph: 3,350 iterations to the settle rule with the
|
|
176
|
+
* kinetic energy still at the clamp; a uniform 1/1000 factor settled in 1,000 with the energy decayed 25x).
|
|
177
|
+
*/
|
|
178
|
+
export const SE_SCALE_REFERENCE_NODES = 300;
|
|
179
|
+
/** Spring-electrical defaults: ngraph.forcelayout 3.3.1's (`lib/createPhysicsSimulator.js:29,34,40,54,59`; spec 7.20), plus the shared simulation defaults. `gravity` is ngraph's Coulomb constant: negative repels. */
|
|
180
|
+
export const SE_DEFAULTS: Readonly<{
|
|
181
|
+
springLength: 10;
|
|
182
|
+
springCoefficient: 0.8;
|
|
183
|
+
gravity: -12;
|
|
184
|
+
dragCoefficient: 0.9;
|
|
185
|
+
timeStep: 0.5;
|
|
186
|
+
dim: 2;
|
|
187
|
+
scale: 1;
|
|
188
|
+
settleThreshold: 0.001;
|
|
189
|
+
settleWindow: 10;
|
|
190
|
+
iterationsPerStep: 1;
|
|
191
|
+
maxInFlight: 2;
|
|
192
|
+
}> = Object.freeze({
|
|
193
|
+
springLength: 10,
|
|
194
|
+
springCoefficient: 0.8,
|
|
195
|
+
gravity: -12,
|
|
196
|
+
dragCoefficient: 0.9,
|
|
197
|
+
timeStep: 0.5,
|
|
198
|
+
dim: 2,
|
|
199
|
+
scale: 1,
|
|
200
|
+
settleThreshold: 0.001,
|
|
201
|
+
settleWindow: 10,
|
|
202
|
+
iterationsPerStep: 1,
|
|
203
|
+
maxInFlight: 2,
|
|
204
|
+
});
|
|
205
|
+
/** The smallest finest grid side `G` (spec 7.7 geometry table: `clamp(nextPow2(2 n^(1/dim)), 8, gridMax)`; P4 PD-9). */
|
|
206
|
+
export const GRID_MIN_SIDE = 8;
|
|
207
|
+
/** The coarsest pyramid level's side (spec 7.7: "levels (coarsest 4 per axis)", `levels = log2(G / 4) + 1`). */
|
|
208
|
+
export const GRID_COARSEST_SIDE = 4;
|
|
209
|
+
/** A cell with more than this many entries is summed by a workgroup (G4b) instead of the thread-per-cell loop of G4 (spec 7.7 G4: `count > 1024`). */
|
|
210
|
+
export const GRID_HUB_CELL = 1024;
|
|
211
|
+
/** The extent floor of spec 7.7: `cellSize = max(extent, 1e-6) / G`, so an all-coincident load never divides by zero. */
|
|
212
|
+
export const GRID_EXTENT_FLOOR = 1e-6;
|
|
213
|
+
/** The bbox margin of spec 7.7: `bboxExtent` is the largest axis of `state.min / max` "with a 1% margin". */
|
|
214
|
+
export const GRID_BBOX_MARGIN = 1.01;
|
|
215
|
+
/** The key width the grid always sorts with (P4 PD-5): three 8-bit passes cover the 19-bit 2D keys at G = 512 and the 22-bit 3D keys at G = 128, and an odd pass count leaves `sortedIdx` in the scratch pair. */
|
|
216
|
+
export const GRID_SORT_BITS = 24;
|
package/src/errors.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* `details` keys per code (contract 3.1, so tests can assert them): E_NO_WEBGPU { reason, hint };
|
|
10
10
|
* E_NO_ADAPTER { reason }; E_NO_DEVICE { reason, adapter, limit?, requested?, available? } (reason "consumed" |
|
|
11
11
|
* "requestDevice" | "limit" | "feature" | "maxComputeWorkgroupsPerDimension"); E_SOFTWARE_ONLY { adapter };
|
|
12
|
-
* E_DEVICE_LOST { reason, message };
|
|
12
|
+
* E_DEVICE_LOST { reason, message }; E_DEVICE_INCORRECT { check, where, expected, actual, poison, count, blocks,
|
|
13
|
+
* workgroupSize, adapter, hint? }; E_DISPOSED { label }; E_VALIDATION { label, message, batchId? };
|
|
13
14
|
* E_SHADER_COMPILE { id, stage: "compose" | "compile", slot?, messages? }; E_OUT_OF_MEMORY { requested, resident,
|
|
14
15
|
* label }; E_TOO_LARGE { needed, limit, path, algorithm }; E_UNSUPPORTED { feature? | option?, hint? } (exactly
|
|
15
16
|
* one of feature / option); E_INVALID_ARGUMENT { argument, value, expected? }; E_SNAPSHOT { reason, serial };
|
|
@@ -23,6 +24,7 @@ export type WebGpuGraphErrorCode =
|
|
|
23
24
|
| "E_NO_DEVICE"
|
|
24
25
|
| "E_SOFTWARE_ONLY"
|
|
25
26
|
| "E_DEVICE_LOST"
|
|
27
|
+
| "E_DEVICE_INCORRECT"
|
|
26
28
|
| "E_DISPOSED"
|
|
27
29
|
| "E_VALIDATION"
|
|
28
30
|
| "E_SHADER_COMPILE"
|
package/src/index.ts
CHANGED
|
@@ -7,20 +7,24 @@
|
|
|
7
7
|
* ForceSimulation, ForceAtlas2Model: tests import them from their files). P0: the error class, the constants and
|
|
8
8
|
* isSoftwareAdapter. P1 adds GpuContext and degree (values) and the context / run / profiler types. P2 adds nothing
|
|
9
9
|
* (Lease, CommandBatch, UniformRing are internal). P3 adds the layout factory, the accelerator, the two default
|
|
10
|
-
* tables, the seeder and the layout / accelerator types.
|
|
11
|
-
*
|
|
10
|
+
* tables, the seeder and the layout / accelerator types. P5 adds the two factories, the two default tables and the
|
|
11
|
+
* two stats records. P4 adds calibrateLayout and its two records. test/index.test.ts pins the value list and
|
|
12
|
+
* test/types/public-api.test-d.ts the type list. This comment must never spell the internal
|
|
12
13
|
* JSDoc tag: it is the leading comment of the first export statement, and stripInternal would drop that statement
|
|
13
14
|
* from the emitted declarations.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
// ==================== constants and errors (P0; FA2_DEFAULTS / LAYOUT_TUNING_DEFAULTS public from P3
|
|
17
|
+
// ==================== constants and errors (P0; FA2_DEFAULTS / LAYOUT_TUNING_DEFAULTS public from P3, FR_DEFAULTS /
|
|
18
|
+
// SE_DEFAULTS from P5)
|
|
17
19
|
export {
|
|
18
20
|
ARC_WINDOW_ALIGN,
|
|
19
21
|
EXACT_MAX_NODES,
|
|
20
22
|
FA2_DEFAULTS,
|
|
23
|
+
FR_DEFAULTS,
|
|
21
24
|
LAYOUT_TUNING_DEFAULTS,
|
|
22
25
|
MAX_1D_ITEMS,
|
|
23
26
|
MAX_WORKGROUPS_PER_DIM,
|
|
27
|
+
SE_DEFAULTS,
|
|
24
28
|
STORAGE_ALIGN,
|
|
25
29
|
WORKGROUP_SIZE,
|
|
26
30
|
} from "./constants.js";
|
|
@@ -37,6 +41,11 @@ export { GpuContext } from "./context.js";
|
|
|
37
41
|
export { isSoftwareAdapter } from "./device/acquire.js";
|
|
38
42
|
export type { PassTiming, Profiler } from "./kernel/profiler.js";
|
|
39
43
|
|
|
44
|
+
// ==================== the device self-check: what this device computed when it was asked an answer we already
|
|
45
|
+
// know. Every compute entry point awaits it and refuses a device that got it wrong (E_DEVICE_INCORRECT); a
|
|
46
|
+
// caller may await it first to ask before committing.
|
|
47
|
+
export { verifyDevice } from "./primitives/verify.js";
|
|
48
|
+
|
|
40
49
|
// ==================== algorithms (P1: the walking-skeleton diagnostic, spec 3.3)
|
|
41
50
|
export { degree } from "./algorithms/degree.js";
|
|
42
51
|
|
|
@@ -45,10 +54,13 @@ export { connectedComponents } from "./algorithms/components.js";
|
|
|
45
54
|
export { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
|
|
46
55
|
export { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
|
|
47
56
|
|
|
48
|
-
// ==================== layouts and the accelerator (P3)
|
|
57
|
+
// ==================== layouts and the accelerator (P3; the two P5 factories; P4's calibrateLayout, spec 2.2)
|
|
49
58
|
export { createAccelerator } from "./accelerator.js";
|
|
59
|
+
export { calibrateLayout } from "./layouts/calibrate.js";
|
|
50
60
|
export { createForceAtlas2 } from "./layouts/forceatlas2.js";
|
|
61
|
+
export { createFruchtermanReingold } from "./layouts/fruchterman-reingold.js";
|
|
51
62
|
export { seedPositions } from "./layouts/seed.js";
|
|
63
|
+
export { createSpringElectrical } from "./layouts/spring-electrical.js";
|
|
52
64
|
|
|
53
65
|
// ==================== types: the accelerator surface and the re-exported declarations of @graphty/layout (spec 9.3)
|
|
54
66
|
// and @graphty/algorithms (spec 9.2), both `import type` since W1b (D27)
|
|
@@ -91,6 +103,8 @@ export type {
|
|
|
91
103
|
export type {
|
|
92
104
|
AdapterInfoLike,
|
|
93
105
|
AdapterSummary,
|
|
106
|
+
DeviceCheck,
|
|
107
|
+
DeviceCheckMismatch,
|
|
94
108
|
GpuCaps,
|
|
95
109
|
GpuContextOptions,
|
|
96
110
|
LimitPolicy,
|
|
@@ -101,14 +115,20 @@ export type {
|
|
|
101
115
|
RaisableLimit,
|
|
102
116
|
} from "./types/context.js";
|
|
103
117
|
|
|
104
|
-
// ==================== types: layouts (P3)
|
|
118
|
+
// ==================== types: layouts (P3; the P5 stats and trace records; the P4 calibration records)
|
|
105
119
|
export type {
|
|
120
|
+
CalibrateOptions,
|
|
106
121
|
ForceAtlas2Stats,
|
|
107
122
|
ForceAtlas2TraceRecord,
|
|
123
|
+
FruchtermanReingoldStats,
|
|
124
|
+
FruchtermanReingoldTraceRecord,
|
|
125
|
+
GpuCalibration,
|
|
108
126
|
GpuLayoutSimulation,
|
|
109
127
|
GpuLayoutTuning,
|
|
110
128
|
LayoutStatsBase,
|
|
111
129
|
RunOptions,
|
|
130
|
+
SpringElectricalStats,
|
|
131
|
+
SpringElectricalTraceRecord,
|
|
112
132
|
} from "./types/layout.js";
|
|
113
133
|
export type {
|
|
114
134
|
CommonLayoutOptions,
|
package/src/kernel/dispatch.ts
CHANGED
|
@@ -147,16 +147,27 @@ export function planGridStride(items: number, wg: number, caps: PlanCaps, maxGro
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* P4 (spec 5.4): the (x, y, 1) args
|
|
151
|
-
*
|
|
152
|
-
*
|
|
150
|
+
* P4 (spec 5.4): the (x, y, 1) args the device-side finalize kernel writes for a count -- plan1d's rule applied to a
|
|
151
|
+
* u32 count through the same grid() as plan1d and plan2d, so the host twin and the kernel cannot drift; the kernel
|
|
152
|
+
* (src/wgsl/indirect-finalize.wgsl.ts) mirrors exactly this arithmetic. `items` of the plan is the count. A count
|
|
153
|
+
* outside [0, 2^32) is E_INVALID_ARGUMENT (the device holds it as a u32); for any u32 count y <= 1,025, so
|
|
154
|
+
* E_TOO_LARGE is unreachable here.
|
|
155
|
+
* @param count - the device-side count (a u32)
|
|
156
|
+
* @param wg - the workgroup size (a power of two)
|
|
153
157
|
* @param caps - the capability table
|
|
158
|
+
* @returns the plan
|
|
154
159
|
*/
|
|
155
160
|
export function planIndirect(count: number, wg: number, caps: PlanCaps): DispatchPlan {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
assertCount("count", count);
|
|
162
|
+
if (count > 0xffffffff) {
|
|
163
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `count must fit a u32, got ${count}`, {
|
|
164
|
+
argument: "count",
|
|
165
|
+
value: count,
|
|
166
|
+
expected: "an integer in [0, 2^32)",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
assertWorkgroupSize(wg);
|
|
170
|
+
return grid(Math.ceil(count / wg), count, caps);
|
|
160
171
|
}
|
|
161
172
|
|
|
162
173
|
/**
|
package/src/kernel/kernel.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Kernel (spec 5.1; contract 3.9): a compiled pipeline bound to the binding list of its spec. `bind()` creates the
|
|
3
3
|
* bind groups from a `Record<name, Binding>` keyed by the declared names and caches them by the identity of the
|
|
4
4
|
* buffers, offsets and sizes (a layout's bind groups are created once per load(), not per iteration); `dispatch()`
|
|
5
|
-
* records setPipeline / setBindGroup / dispatchWorkgroups and skips an empty plan (spec 5.6)
|
|
5
|
+
* records setPipeline / setBindGroup / dispatchWorkgroups and skips an empty plan (spec 5.6); `dispatchIndirect()`
|
|
6
|
+
* (P4, spec 5.4) records the same prefix and a dispatchWorkgroupsIndirect over one 16-byte slot of an args buffer.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { STORAGE_ALIGN } from "../constants.js";
|
|
@@ -21,6 +22,9 @@ export interface BoundKernel {
|
|
|
21
22
|
readonly dynamicGroups: readonly number[];
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** Bytes of one indirect args slot: (x, y, 1, count) as four u32 (spec 5.4; P4 PD-2). */
|
|
26
|
+
export const INDIRECT_ARGS_STRIDE = 16;
|
|
27
|
+
|
|
24
28
|
/** Buffer identities for the bind-group cache key: every GPUBuffer seen by any Kernel gets one number, once. */
|
|
25
29
|
const bufferIds = new WeakMap<GPUBuffer, number>();
|
|
26
30
|
let nextBufferId = 1;
|
|
@@ -259,6 +263,50 @@ export class Kernel {
|
|
|
259
263
|
plan: DispatchPlan,
|
|
260
264
|
dynamicOffsets?: readonly number[],
|
|
261
265
|
): void {
|
|
266
|
+
this.check(bound, dynamicOffsets);
|
|
267
|
+
if (plan.x === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.setUp(pass, bound, dynamicOffsets);
|
|
271
|
+
pass.dispatchWorkgroups(plan.x, plan.y, 1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* setPipeline + the bind groups exactly as dispatch(), then dispatchWorkgroupsIndirect(args.buffer, args.offset +
|
|
276
|
+
* INDIRECT_ARGS_STRIDE * slot) (spec 5.4): the (x, y, 1) of the slot were written by the indirect-finalize kernel
|
|
277
|
+
* earlier in the same pass. A slot beyond the binding is E_INVALID_ARGUMENT.
|
|
278
|
+
* @param pass - the open compute pass
|
|
279
|
+
* @param bound - a BoundKernel of THIS kernel
|
|
280
|
+
* @param args - the args buffer range (usage INDIRECT | STORAGE)
|
|
281
|
+
* @param slot - the 16-byte slot index inside the range
|
|
282
|
+
* @param dynamicOffsets - one byte offset per entry of bound.dynamicGroups
|
|
283
|
+
*/
|
|
284
|
+
dispatchIndirect(
|
|
285
|
+
pass: GPUComputePassEncoder,
|
|
286
|
+
bound: BoundKernel,
|
|
287
|
+
args: Binding,
|
|
288
|
+
slot: number,
|
|
289
|
+
dynamicOffsets?: readonly number[],
|
|
290
|
+
): void {
|
|
291
|
+
this.check(bound, dynamicOffsets);
|
|
292
|
+
if (!Number.isInteger(slot) || slot < 0 || (slot + 1) * INDIRECT_ARGS_STRIDE > args.size) {
|
|
293
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${this.spec.id}: args slot ${slot} is outside the binding`, {
|
|
294
|
+
argument: "slot",
|
|
295
|
+
value: slot,
|
|
296
|
+
expected: `0 <= slot < ${Math.floor(args.size / INDIRECT_ARGS_STRIDE)}`,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
this.setUp(pass, bound, dynamicOffsets);
|
|
300
|
+
pass.dispatchWorkgroupsIndirect(args.buffer, args.offset + INDIRECT_ARGS_STRIDE * slot);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The argument checks dispatch() and dispatchIndirect() share: a BoundKernel of another kernel or an offset list
|
|
305
|
+
* of the wrong length is E_INVALID_ARGUMENT.
|
|
306
|
+
* @param bound - the bound kernel
|
|
307
|
+
* @param dynamicOffsets - the offsets, if any
|
|
308
|
+
*/
|
|
309
|
+
private check(bound: BoundKernel, dynamicOffsets: readonly number[] | undefined): void {
|
|
262
310
|
const { id } = this.spec;
|
|
263
311
|
if (bound.kernel !== this) {
|
|
264
312
|
throw new WebGpuGraphError(
|
|
@@ -282,9 +330,16 @@ export class Kernel {
|
|
|
282
330
|
},
|
|
283
331
|
);
|
|
284
332
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* setPipeline + setBindGroup for every group (the dynamic offsets replicated over the uniform bindings of each
|
|
337
|
+
* dynamic group), the prefix dispatch() and dispatchIndirect() share.
|
|
338
|
+
* @param pass - the open compute pass
|
|
339
|
+
* @param bound - a BoundKernel of this kernel
|
|
340
|
+
* @param dynamicOffsets - one byte offset per entry of bound.dynamicGroups (absent = 0)
|
|
341
|
+
*/
|
|
342
|
+
private setUp(pass: GPUComputePassEncoder, bound: BoundKernel, dynamicOffsets: readonly number[] | undefined): void {
|
|
288
343
|
pass.setPipeline(this.pipeline);
|
|
289
344
|
let next = 0;
|
|
290
345
|
bound.bindGroups.forEach((bindGroup, group) => {
|
|
@@ -301,7 +356,6 @@ export class Kernel {
|
|
|
301
356
|
pass.setBindGroup(group, bindGroup);
|
|
302
357
|
}
|
|
303
358
|
});
|
|
304
|
-
pass.dispatchWorkgroups(plan.x, plan.y, 1);
|
|
305
359
|
}
|
|
306
360
|
|
|
307
361
|
/** Drops cached bind groups (a layout's buffers changed). */
|
package/src/kernel/prelude.ts
CHANGED
|
@@ -15,8 +15,15 @@ import {
|
|
|
15
15
|
FA2_COINCIDENT_SQ,
|
|
16
16
|
FA2_DISTANCE_FLOOR,
|
|
17
17
|
FA2_DISTANCE_FLOOR_SQ,
|
|
18
|
+
FA2_FLAG_ADAPTIVE,
|
|
18
19
|
FA2_FLAG_FIRST,
|
|
20
|
+
FR_COOLING_PATIENCE,
|
|
21
|
+
FR_COOLING_STEP,
|
|
22
|
+
GRID_BBOX_MARGIN,
|
|
23
|
+
GRID_EXTENT_FLOOR,
|
|
24
|
+
GRID_HUB_CELL,
|
|
19
25
|
MAX_WORKGROUPS_PER_DIM,
|
|
26
|
+
RADIX_BINS,
|
|
20
27
|
U32_MAX,
|
|
21
28
|
WORKGROUP_SIZE,
|
|
22
29
|
} from "../constants.js";
|
|
@@ -49,6 +56,14 @@ const FA2_DIST_FLOOR: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR)};
|
|
|
49
56
|
const FA2_DIST_FLOOR_SQ: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR_SQ)};
|
|
50
57
|
const FA2_COINCIDENT_SQ: f32 = ${wgslF32Literal(FA2_COINCIDENT_SQ)};
|
|
51
58
|
const FA2_FLAG_FIRST: u32 = ${FA2_FLAG_FIRST}u;
|
|
59
|
+
const FA2_FLAG_ADAPTIVE: u32 = ${FA2_FLAG_ADAPTIVE}u;
|
|
60
|
+
const FR_COOLING_STEP: f32 = ${wgslF32Literal(FR_COOLING_STEP)};
|
|
61
|
+
const FR_COOLING_PATIENCE: u32 = ${FR_COOLING_PATIENCE}u;
|
|
62
|
+
const GRID_HUB_CELL: u32 = ${GRID_HUB_CELL}u;
|
|
63
|
+
const GRID_EXTENT_FLOOR: f32 = ${wgslF32Literal(GRID_EXTENT_FLOOR)};
|
|
64
|
+
const GRID_BBOX_MARGIN: f32 = ${wgslF32Literal(GRID_BBOX_MARGIN)};
|
|
65
|
+
const RADIX_BINS: u32 = ${RADIX_BINS}u;
|
|
66
|
+
const RADIX_DIGIT_MASK: u32 = ${RADIX_BINS - 1}u;
|
|
52
67
|
const F32_MAX: f32 = 0x1.fffffep+127;
|
|
53
68
|
override WG: u32 = ${WORKGROUP_SIZE}u;
|
|
54
69
|
override USE_PERM: bool = false;
|
package/src/kernel/profiler.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* every CommandBatch pass; `resolveInto(batch)` records the resolve plus a copy into the batch's
|
|
5
5
|
* staging slot and `timings(bytes, request)` decodes `{ label, ns }` per pass from the batch's
|
|
6
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
|
|
8
|
-
*
|
|
7
|
+
* 1,024 ns ticks (spec 2.6). The profiler never throws: a full query set drops the timings of the
|
|
8
|
+
* passes past the budget and marks the batch's request `partial(request)` so a caller summing the
|
|
9
|
+
* rows into a duration falls back to wall time; a device without the feature disables it. Slot
|
|
10
|
+
* pairs are handed out in order and the cursor restarts at 0
|
|
9
11
|
* after every resolve: queue order guarantees the resolve of batch A reads its slots before batch B
|
|
10
12
|
* overwrites them, and one resolve buffer suffices for the same reason.
|
|
11
13
|
*/
|
|
@@ -36,8 +38,10 @@ export class Profiler {
|
|
|
36
38
|
private readonly querySet: GPUQuerySet | null;
|
|
37
39
|
private readonly resolveBuffer: GPUBuffer | null;
|
|
38
40
|
private readonly labels = new WeakMap<ReadbackRequest, readonly string[]>();
|
|
41
|
+
private readonly partialRequests = new WeakSet<ReadbackRequest>();
|
|
39
42
|
private pending: PendingPass[] = [];
|
|
40
43
|
private cursor = 0;
|
|
44
|
+
private refused = 0;
|
|
41
45
|
private destroyed = false;
|
|
42
46
|
|
|
43
47
|
/**
|
|
@@ -69,12 +73,17 @@ export class Profiler {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
|
-
* The timestampWrites descriptor for a new pass, or undefined when disabled / out of slots.
|
|
76
|
+
* The timestampWrites descriptor for a new pass, or undefined when disabled / out of slots. A pass refused for
|
|
77
|
+
* want of slots is counted, and the next `resolveInto()` marks its request partial.
|
|
73
78
|
* @param label - the pass name reported by `timings()`
|
|
74
79
|
* @returns the descriptor a CommandBatch puts on beginComputePass, or undefined
|
|
75
80
|
*/
|
|
76
81
|
beginPass(label: string): GPUComputePassTimestampWrites | undefined {
|
|
77
|
-
if (this.querySet === null || this.destroyed
|
|
82
|
+
if (this.querySet === null || this.destroyed) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (this.cursor + 2 > this.slots) {
|
|
86
|
+
this.refused++;
|
|
78
87
|
return undefined;
|
|
79
88
|
}
|
|
80
89
|
const begin = this.cursor;
|
|
@@ -100,11 +109,25 @@ export class Profiler {
|
|
|
100
109
|
request,
|
|
101
110
|
this.pending.map((pass) => pass.label),
|
|
102
111
|
);
|
|
112
|
+
if (this.refused > 0) {
|
|
113
|
+
this.partialRequests.add(request);
|
|
114
|
+
}
|
|
103
115
|
this.pending = [];
|
|
104
116
|
this.cursor = 0;
|
|
117
|
+
this.refused = 0;
|
|
105
118
|
return request;
|
|
106
119
|
}
|
|
107
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Whether a pass of the batch was refused a slot pair, so `timings()` covers only its first slots / 2 passes
|
|
123
|
+
* and their sum is not the batch's duration.
|
|
124
|
+
* @param request - the request `resolveInto()` returned
|
|
125
|
+
* @returns true when at least one pass of that batch went untimed
|
|
126
|
+
*/
|
|
127
|
+
partial(request: ReadbackRequest): boolean {
|
|
128
|
+
return this.partialRequests.has(request);
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
/**
|
|
109
132
|
* Decodes the timings of a batch from its readback bytes.
|
|
110
133
|
* @param bytes - the resolved readback of the batch
|
|
@@ -138,6 +161,7 @@ export class Profiler {
|
|
|
138
161
|
this.destroyed = true;
|
|
139
162
|
this.pending = [];
|
|
140
163
|
this.cursor = 0;
|
|
164
|
+
this.refused = 0;
|
|
141
165
|
if (this.querySet !== null) {
|
|
142
166
|
this.querySet.destroy();
|
|
143
167
|
}
|