@graphty/webgpu-graph-algorithms 0.5.1 → 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 +98 -52
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-BR7fx3vR.js → context-BXqgCifx.js} +190 -40
- package/dist/chunks/context-BXqgCifx.js.map +1 -0
- package/dist/node.js +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 +38 -8
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +38 -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 +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- 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 +6 -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 +17 -7
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +323 -16
- 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 +39 -4
- package/dist/src/layouts/force-simulation.d.ts.map +1 -1
- package/dist/src/layouts/force-simulation.js +71 -19
- package/dist/src/layouts/force-simulation.js.map +1 -1
- package/dist/src/layouts/forceatlas2.d.ts +107 -36
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
- package/dist/src/layouts/forceatlas2.js +296 -100
- package/dist/src/layouts/forceatlas2.js.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.d.ts +73 -27
- package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.js +230 -70
- package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/model-common.d.ts +41 -3
- package/dist/src/layouts/model-common.d.ts.map +1 -1
- package/dist/src/layouts/model-common.js +74 -3
- package/dist/src/layouts/model-common.js.map +1 -1
- 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 +75 -30
- package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
- package/dist/src/layouts/spring-electrical.js +231 -74
- package/dist/src/layouts/spring-electrical.js.map +1 -1
- 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/context.d.ts +53 -0
- package/dist/src/types/context.d.ts.map +1 -1
- package/dist/src/types/layout.d.ts +20 -0
- package/dist/src/types/layout.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 -11
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js +98 -20
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +6 -2
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +22 -1
- 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 +3815 -1003
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +5 -4
- 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 +38 -8
- package/src/errors.ts +3 -1
- package/src/index.ts +14 -4
- package/src/kernel/dispatch.ts +18 -7
- package/src/kernel/kernel.ts +59 -5
- package/src/kernel/prelude.ts +9 -0
- package/src/kernel/profiler.ts +28 -4
- package/src/kernels.ts +356 -18
- package/src/layouts/calibrate.ts +187 -0
- package/src/layouts/force-simulation.ts +91 -23
- package/src/layouts/forceatlas2.ts +331 -106
- package/src/layouts/fruchterman-reingold.ts +255 -74
- package/src/layouts/model-common.ts +98 -3
- package/src/layouts/repulsion-grid.ts +451 -0
- package/src/layouts/spring-electrical.ts +257 -78
- 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/context.ts +56 -0
- package/src/types/layout.ts +22 -0
- package/src/wgsl/counting-scatter.wgsl.ts +16 -0
- package/src/wgsl/fa2-attraction.wgsl.ts +98 -20
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +22 -1
- 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-BR7fx3vR.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The spring-electrical preset on the exact repulsion tier (spec 7.20 "the preset"; contract 3.13):
|
|
3
|
-
* physics under ngraph's option names and defaults -- Coulomb repulsion `-g m_i m_j / d^2` (K3,
|
|
2
|
+
* The spring-electrical preset on the exact or the grid repulsion tier (spec 7.20 "the preset", 7.8; contract 3.13):
|
|
3
|
+
* ngraph.forcelayout's physics under ngraph's option names and defaults -- Coulomb repulsion `-g m_i m_j / d^2` (K3,
|
|
4
|
+
* LAW 2; G6 / G7 with LAW 2 on the grid tier, P4-T13), Hooke springs
|
|
4
5
|
* `k_s (d - L)` (K2, LAW 2), drag and the semi-implicit Euler step with the unit speed clamp over a per-node velocity
|
|
5
6
|
* (K5, APPLY 2), the kinetic energy folded into the trace (K1, STATS_MODE 2; PD-4) -- as the ForceModel that
|
|
6
7
|
* ForceSimulation drives over the four FA2 kernels K1 K2 K3 K5 per iteration and toScene per batch. No K4: there is no
|
|
@@ -10,9 +11,12 @@
|
|
|
10
11
|
* `Fa2Params.coulomb` while FA2's centre gravity is 0 (PD-12). Settlement is the shared rule of spec 7.17
|
|
11
12
|
* (DEP-P5-A); `reheat()` leaves the velocities alone (ngraph has no reheat).
|
|
12
13
|
*
|
|
13
|
-
* Model decisions this file shares with forceatlas2.ts: the K1-K5 dispatches of one batch share ONE
|
|
14
|
-
* toScene runs in a second pass that ends it; the fill kernel takes its FillParams from a model-owned
|
|
15
|
-
* buffer
|
|
14
|
+
* Model decisions this file shares with forceatlas2.ts: on the exact tier the K1-K5 dispatches of one batch share ONE
|
|
15
|
+
* compute pass and toScene runs in a second pass that ends it; the fill kernel takes its FillParams from a model-owned
|
|
16
|
+
* 256-byte uniform buffer. The grid tier (P4-T13, PD-22) is FA2's: `RepulsionGrid` with `LAW` 2 when `tierFor(tuning,
|
|
17
|
+
* n)` says so (PD-18), the grid buffers from `buffers()`, K1's grid block under `gridMax > 0` (PD-14) over the model's
|
|
18
|
+
* own `hubCounters`, the three passes `se-k1` / `se-attraction` / `se-grid` before `se-to-scene` (PD-16), and the
|
|
19
|
+
* union stage list (PD-17; K4 is never recorded).
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
import { type GraphSnapshot } from "@graphty/graph-format";
|
|
@@ -32,7 +36,9 @@ import { type DispatchPlan, plan1d } from "../kernel/dispatch.js";
|
|
|
32
36
|
import { type BoundKernel, type Kernel } from "../kernel/kernel.js";
|
|
33
37
|
import { type UniformBlock, type UniformValues } from "../kernel/struct-block.js";
|
|
34
38
|
import { type WgslModuleSpec } from "../kernel/wgsl.js";
|
|
35
|
-
import { FA2_PARAMS, FA2_STATE, FA2_TRACE, FILL_PARAMS,
|
|
39
|
+
import { FA2_PARAMS, FA2_STATE, FA2_TRACE, FILL_PARAMS, kernelSpec } from "../kernels.js";
|
|
40
|
+
import { arcCountOf } from "../primitives/core-shape.js";
|
|
41
|
+
import { type GridSpec, gridSpecFor } from "../primitives/grid.js";
|
|
36
42
|
import {
|
|
37
43
|
type GpuLayoutSimulation,
|
|
38
44
|
type GpuLayoutTuning,
|
|
@@ -49,9 +55,12 @@ import {
|
|
|
49
55
|
type ModelInputs,
|
|
50
56
|
type ModelResources,
|
|
51
57
|
type StateWriter,
|
|
58
|
+
tierFor,
|
|
52
59
|
} from "./force-simulation.js";
|
|
53
|
-
import { resolveLayoutTuning } from "./forceatlas2.js";
|
|
60
|
+
import { resolveLayoutTuning, writeGridFrame } from "./forceatlas2.js";
|
|
54
61
|
import {
|
|
62
|
+
type AttractionBound,
|
|
63
|
+
bindAttraction,
|
|
55
64
|
describeValue,
|
|
56
65
|
FILL_PARAMS_BUFFER,
|
|
57
66
|
FORCE_BYTES_PER_NODE,
|
|
@@ -62,16 +71,27 @@ import {
|
|
|
62
71
|
pickDim,
|
|
63
72
|
pickNumber,
|
|
64
73
|
pickSeed,
|
|
74
|
+
recordAttraction,
|
|
65
75
|
scalar,
|
|
66
76
|
seedWord,
|
|
67
77
|
subset,
|
|
68
78
|
vector,
|
|
69
79
|
} from "./model-common.js";
|
|
80
|
+
import { type GridStage, RepulsionGrid, type RepulsionGridOverrides } from "./repulsion-grid.js";
|
|
70
81
|
|
|
71
82
|
// ============================================================ constants
|
|
72
83
|
|
|
73
|
-
/** The stage names of
|
|
74
|
-
const SE_STAGES = ["K1", "K2", "K3", "K5", "toScene"] as const;
|
|
84
|
+
/** The stage names of both tiers in dispatch order plus the per-batch toScene (spec 7.20; P4 PD-17: the union list; the exact tier records K1 K2 K3 K5, the grid tier K1 K2 G1..G7 K5, K4 never). */
|
|
85
|
+
const SE_STAGES = ["K1", "K2", "K3", "G1", "G2", "G3", "G4", "G5", "G6", "G7", "K4", "K5", "toScene"] as const;
|
|
86
|
+
|
|
87
|
+
/** The SE_STAGES index of the first grid stage, of K4, of K5 and of toScene. */
|
|
88
|
+
const STAGE_G1 = 3;
|
|
89
|
+
const STAGE_K4 = 10;
|
|
90
|
+
const STAGE_K5 = 11;
|
|
91
|
+
const STAGE_TO_SCENE = 12;
|
|
92
|
+
|
|
93
|
+
/** The name of the model-owned hub-counter buffer K1 binds on every tier (P4 PD-14). */
|
|
94
|
+
const HUB_COUNTERS_BUFFER = "hubCounters";
|
|
75
95
|
|
|
76
96
|
/** The one-workgroup dispatch of K1. */
|
|
77
97
|
const ONE_WORKGROUP: DispatchPlan = { x: 1, y: 1, z: 1, items: 1, stride: null };
|
|
@@ -89,6 +109,14 @@ const SE_OVERRIDES: Overrides = Object.freeze({
|
|
|
89
109
|
STATS_MODE: 2,
|
|
90
110
|
});
|
|
91
111
|
|
|
112
|
+
/** The grid stage's override set (G6 / G7 / K4; P4-T13, PD-22): K3's constant three and the coulomb law. */
|
|
113
|
+
const SE_GRID_OVERRIDES: RepulsionGridOverrides = Object.freeze({
|
|
114
|
+
SWING_MODE: 1,
|
|
115
|
+
STRONG_GRAVITY: false,
|
|
116
|
+
GRAVITY_CENTER: 0,
|
|
117
|
+
LAW: 2,
|
|
118
|
+
});
|
|
119
|
+
|
|
92
120
|
/** Every override K1 accepts, with its default. */
|
|
93
121
|
const K1_DEFAULTS: Overrides = { STATS_MODE: 0 };
|
|
94
122
|
|
|
@@ -229,11 +257,13 @@ interface BoundModel {
|
|
|
229
257
|
readonly fillPlan: DispatchPlan;
|
|
230
258
|
readonly k1: Kernel;
|
|
231
259
|
readonly k1Bound: BoundKernel;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
readonly k3: Kernel;
|
|
236
|
-
readonly k3Bound: BoundKernel;
|
|
260
|
+
/** The K2 tier dispatches (P4 PD-7); null when arcCount === 0 (K2 is not recorded; the fill below zeroes force instead). */
|
|
261
|
+
readonly attraction: AttractionBound | null;
|
|
262
|
+
/** K3 and its bind group, or null on the grid tier (P4 PD-18): one tier's kernels compile per load. */
|
|
263
|
+
readonly k3: Kernel | null;
|
|
264
|
+
readonly k3Bound: BoundKernel | null;
|
|
265
|
+
/** The grid-tier stage (G1-G7), or null on the exact tier (P4 PD-18). */
|
|
266
|
+
readonly grid: RepulsionGrid | null;
|
|
237
267
|
readonly k5: Kernel;
|
|
238
268
|
readonly k5Bound: BoundKernel;
|
|
239
269
|
readonly toScene: Kernel;
|
|
@@ -258,12 +288,12 @@ function massOf(s: GraphSnapshot): Float32Array<ArrayBuffer> {
|
|
|
258
288
|
return out;
|
|
259
289
|
}
|
|
260
290
|
|
|
261
|
-
/** The spring-electrical model (spec 7.20: K1 K2 K3 K5 per iteration; toScene once per batch). Stages:
|
|
291
|
+
/** The spring-electrical model (spec 7.20: K1 K2 K3 K5 per iteration on the exact tier, K1 K2 G1..G7 K5 on the grid tier; toScene once per batch). Stages: the union list of PD-17. */
|
|
262
292
|
export class SpringElectricalModel implements ForceModel<SpringElectricalOptions, SpringElectricalStats> {
|
|
263
293
|
/** The model kind of spec 7.19. */
|
|
264
294
|
readonly kind = "springElectrical";
|
|
265
295
|
/** The stage names in dispatch order (the `upTo` vocabulary of recordIteration and debugRunStages). */
|
|
266
|
-
readonly stages:
|
|
296
|
+
readonly stages: typeof SE_STAGES = SE_STAGES;
|
|
267
297
|
/** Fa2Params: the per-iteration uniform block (the simulation writes the shared fields into it). */
|
|
268
298
|
readonly params: UniformBlock = FA2_PARAMS;
|
|
269
299
|
/** Fa2State: the state header block. */
|
|
@@ -279,6 +309,8 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
279
309
|
private resources: ModelResources | null = null;
|
|
280
310
|
/** The kernels and bind groups of the last bind(), or null before it (and for n === 0). */
|
|
281
311
|
private bound: BoundModel | null = null;
|
|
312
|
+
/** The grid of the load inputs() last resolved (null on the exact tier): onLoad() writes its frame, specs() lists its kernels. */
|
|
313
|
+
private nextGrid: GridSpec | null = null;
|
|
282
314
|
/** The K1-K5 compute pass of the batch being recorded, keyed by CommandBatch.id; null between batches. */
|
|
283
315
|
private openPass: { readonly id: number; readonly pass: GPUComputePassEncoder } | null = null;
|
|
284
316
|
|
|
@@ -294,15 +326,18 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
294
326
|
|
|
295
327
|
/**
|
|
296
328
|
* force 12n (zeroed), velocity 12n (zeroed: every load() starts at rest, `allocate()` / `clearKept()` honour
|
|
297
|
-
* `zero`)
|
|
298
|
-
*
|
|
329
|
+
* `zero`), the 256-byte FillParams uniform buffer, the 16-byte `hubCounters` K1 binds on every tier (P4 PD-14),
|
|
330
|
+
* and the grid buffers of `RepulsionGrid.buffers` exactly when `tierFor(tuning, n)` is the grid tier (PD-18).
|
|
331
|
+
* n = 0 reports one node's worth of bytes so no zero-length buffer is ever created.
|
|
299
332
|
* @param n - the node count
|
|
300
|
-
* @param
|
|
301
|
-
* @returns the
|
|
333
|
+
* @param dim - the layout dimension (the arrays are stride 3 in both; the grid's geometry differs)
|
|
334
|
+
* @returns the model-owned buffer specs
|
|
302
335
|
*/
|
|
303
|
-
buffers(n: number,
|
|
336
|
+
buffers(n: number, dim: 2 | 3): readonly BufferSpec[] {
|
|
304
337
|
const bytes = Math.max(1, n) * FORCE_BYTES_PER_NODE;
|
|
305
338
|
const usage = BufferUsage.STORAGE | BufferUsage.COPY_SRC | BufferUsage.COPY_DST;
|
|
339
|
+
const grid =
|
|
340
|
+
tierFor(this.tuning, n) === "grid" ? RepulsionGrid.buffers(n, gridSpecFor(n, dim, this.tuning)) : [];
|
|
306
341
|
return [
|
|
307
342
|
{ name: "force", byteLength: bytes, usage, zero: true },
|
|
308
343
|
{ name: VELOCITY_BUFFER, byteLength: bytes, usage, zero: true },
|
|
@@ -312,18 +347,24 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
312
347
|
usage: BufferUsage.UNIFORM | BufferUsage.COPY_DST,
|
|
313
348
|
zero: false,
|
|
314
349
|
},
|
|
350
|
+
{ name: HUB_COUNTERS_BUFFER, byteLength: 16, usage, zero: true },
|
|
351
|
+
...grid,
|
|
315
352
|
];
|
|
316
353
|
}
|
|
317
354
|
|
|
318
355
|
/**
|
|
319
356
|
* { mass: 1 + degree / 3, weights: none } (PD-11): the preset has no mass or weight option, and K2 compiles
|
|
320
|
-
* HAS_WEIGHTS = false. No `fixed` (setFixed is the live API).
|
|
321
|
-
*
|
|
357
|
+
* HAS_WEIGHTS = false. No `fixed` (setFixed is the live API). Also remembers the grid of this load
|
|
358
|
+
* (`tierFor(tuning, n)`, spec 7.8) for onLoad() and specs(): the simulation calls inputs() first, then onLoad()
|
|
359
|
+
* before bind().
|
|
322
360
|
* @param s - the snapshot being loaded
|
|
323
|
-
* @param
|
|
361
|
+
* @param options - the simulation's current option record (its dimension picks the grid's geometry)
|
|
324
362
|
* @returns the per-load inputs
|
|
325
363
|
*/
|
|
326
|
-
inputs(s: GraphSnapshot,
|
|
364
|
+
inputs(s: GraphSnapshot, options: SpringElectricalOptions): ModelInputs {
|
|
365
|
+
const { dim } = resolveSpringElectricalOptions(options, this.current);
|
|
366
|
+
const n = s.nodeCount;
|
|
367
|
+
this.nextGrid = tierFor(this.tuning, n) === "grid" ? gridSpecFor(n, dim, this.tuning) : null;
|
|
327
368
|
return { mass: massOf(s), weights: { data: null, source: "none", column: null } };
|
|
328
369
|
}
|
|
329
370
|
|
|
@@ -338,12 +379,15 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
338
379
|
|
|
339
380
|
/**
|
|
340
381
|
* The six module specs of an override set in dispatch order -- K1, K2, K3, K5, toScene, fill -- each with only the
|
|
341
|
-
* override names its entry declares (K2 also USE_PERM / HAS_WEIGHTS), for warm() and the compile matrix
|
|
382
|
+
* override names its entry declares (K2 also USE_PERM / HAS_WEIGHTS), for warm() and the compile matrix,
|
|
383
|
+
* followed by the grid tier's specs (`RepulsionGrid.specs` under SE_GRID_OVERRIDES) when the load inputs() last
|
|
384
|
+
* resolved is a grid load (the pipeline key carries no geometry).
|
|
342
385
|
* @param overrides - the merged override set (the model's plus USE_PERM / HAS_WEIGHTS)
|
|
343
386
|
* @param _subgroups - accepted for the ForceModel interface and unused (the composer picks the twin from caps)
|
|
344
387
|
* @returns the specs
|
|
345
388
|
*/
|
|
346
389
|
specs(overrides: Overrides, _subgroups: boolean): readonly WgslModuleSpec[] {
|
|
390
|
+
const grid = this.nextGrid === null ? [] : RepulsionGrid.specs(SE_GRID_OVERRIDES, this.nextGrid);
|
|
347
391
|
return [
|
|
348
392
|
kernelSpec("fa2-stats-finalize", subset(overrides, K1_DEFAULTS)),
|
|
349
393
|
kernelSpec("fa2-attraction", subset(overrides, K2_DEFAULTS)),
|
|
@@ -351,45 +395,64 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
351
395
|
kernelSpec("fa2-integrate", subset(overrides, K5_DEFAULTS)),
|
|
352
396
|
kernelSpec("fa2-to-scene"),
|
|
353
397
|
kernelSpec("fill"),
|
|
398
|
+
...grid,
|
|
354
399
|
];
|
|
355
400
|
}
|
|
356
401
|
|
|
357
402
|
/**
|
|
358
|
-
* Compiles (through the cache) and binds every kernel against the buffers of this load(): K1, K2
|
|
359
|
-
* force when arcCount === 0), K3
|
|
360
|
-
*
|
|
403
|
+
* Compiles (through the cache) and binds every kernel against the buffers of this load(): K1, K2 over the degree
|
|
404
|
+
* tiers through bindAttraction (or the fill of force when arcCount === 0), K3 on the exact tier or G1-G7 through
|
|
405
|
+
* RepulsionGrid on the grid tier (PD-18), K5, with the velocity in the `oldForce` slot of K3 / G7 / K5 (PD-2),
|
|
406
|
+
* toScene; writes the FillParams { count: 3n, value: 0, mode: 0 } into the model's uniform buffer. With n === 0
|
|
407
|
+
* nothing is bound. The K2 TIER 1 / 2 pipelines compile on the first load whose degrees need them (P4 PD-7).
|
|
361
408
|
* @param resources - the graph, the shared and model buffers, the ring and the cache
|
|
362
409
|
* @param overrides - the merged override set
|
|
363
410
|
*/
|
|
364
411
|
async bind(resources: ModelResources, overrides: Overrides): Promise<void> {
|
|
365
412
|
this.dropBound();
|
|
366
413
|
this.resources = resources;
|
|
367
|
-
const { n, pipelines, caps, core,
|
|
414
|
+
const { n, pipelines, caps, core, ring, device } = resources;
|
|
368
415
|
if (n === 0) {
|
|
369
416
|
return;
|
|
370
417
|
}
|
|
371
|
-
const
|
|
418
|
+
const pos = resources.buffer("positions");
|
|
419
|
+
const force = resources.buffer("force");
|
|
420
|
+
const params = ring.binding(FA2_PARAMS);
|
|
421
|
+
const hasArcs = core.colIdx !== null;
|
|
422
|
+
const [k1, k5, toScene, fill] = await Promise.all([
|
|
372
423
|
pipelines.kernel(kernelSpec("fa2-stats-finalize", subset(overrides, K1_DEFAULTS))),
|
|
373
|
-
pipelines.kernel(kernelSpec("fa2-attraction", subset(overrides, K2_DEFAULTS))),
|
|
374
|
-
pipelines.kernel(kernelSpec("fa2-repulsion-exact", subset(overrides, K3_DEFAULTS))),
|
|
375
424
|
pipelines.kernel(kernelSpec("fa2-integrate", subset(overrides, K5_DEFAULTS))),
|
|
376
425
|
pipelines.kernel(kernelSpec("fa2-to-scene")),
|
|
377
426
|
pipelines.kernel(kernelSpec("fill")),
|
|
378
427
|
]);
|
|
428
|
+
const k3 =
|
|
429
|
+
resources.tier === "grid"
|
|
430
|
+
? null
|
|
431
|
+
: await pipelines.kernel(kernelSpec("fa2-repulsion-exact", subset(overrides, K3_DEFAULTS)));
|
|
432
|
+
const attraction = hasArcs
|
|
433
|
+
? await bindAttraction(resources, subset(overrides, K2_DEFAULTS), { pos, force, params })
|
|
434
|
+
: null;
|
|
435
|
+
const grid =
|
|
436
|
+
resources.tier === "grid"
|
|
437
|
+
? await RepulsionGrid.create(
|
|
438
|
+
resources,
|
|
439
|
+
k1.workgroupSize,
|
|
440
|
+
SE_GRID_OVERRIDES,
|
|
441
|
+
gridSpecFor(n, resources.dim, this.tuning),
|
|
442
|
+
)
|
|
443
|
+
: null;
|
|
379
444
|
if (this.resources !== resources) {
|
|
380
445
|
// a newer bind() superseded this one while the pipelines compiled; its own bind groups stand
|
|
446
|
+
grid?.dispose();
|
|
381
447
|
return;
|
|
382
448
|
}
|
|
383
|
-
const pos = resources.buffer("positions");
|
|
384
449
|
const scene = resources.buffer("scenePositions");
|
|
385
450
|
const fixed = resources.buffer("fixed");
|
|
386
451
|
const partials = resources.buffer("partials");
|
|
387
452
|
const state = resources.buffer("state");
|
|
388
453
|
const trace = resources.buffer("trace");
|
|
389
|
-
const force = resources.buffer("force");
|
|
390
454
|
const velocity = resources.buffer(VELOCITY_BUFFER);
|
|
391
455
|
const fillParamsBuffer = resources.buffer(FILL_PARAMS_BUFFER);
|
|
392
|
-
const params = ring.binding(FA2_PARAMS);
|
|
393
456
|
const fillParams: Binding = {
|
|
394
457
|
buffer: fillParamsBuffer.buffer,
|
|
395
458
|
offset: fillParamsBuffer.offset,
|
|
@@ -399,20 +462,48 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
399
462
|
const fillBytes = new ArrayBuffer(FILL_PARAMS.byteLength);
|
|
400
463
|
FILL_PARAMS.write(new DataView(fillBytes), { count: 3 * n, value: 0, mode: 0 });
|
|
401
464
|
device.queue.writeBuffer(fillParamsBuffer.buffer, fillParamsBuffer.offset, fillBytes);
|
|
402
|
-
const
|
|
465
|
+
const hubCounters = resources.buffer(HUB_COUNTERS_BUFFER);
|
|
466
|
+
grid?.bind({
|
|
467
|
+
pos,
|
|
468
|
+
state,
|
|
469
|
+
trace,
|
|
470
|
+
force,
|
|
471
|
+
oldForce: velocity,
|
|
472
|
+
fixedMask: fixed,
|
|
473
|
+
partials,
|
|
474
|
+
params,
|
|
475
|
+
cellKey: resources.buffer("cellKey"),
|
|
476
|
+
cellVal: resources.buffer("cellVal"),
|
|
477
|
+
sortedKey: resources.buffer("sortedKey"),
|
|
478
|
+
sortedIdx: resources.buffer("sortedIdx"),
|
|
479
|
+
cellHist: resources.buffer("cellHist"),
|
|
480
|
+
cellStart: resources.buffer("cellStart"),
|
|
481
|
+
hubList: resources.buffer("hubList"),
|
|
482
|
+
hubCounters,
|
|
483
|
+
hubArgs: resources.buffer("hubArgs"),
|
|
484
|
+
pyramid: resources.buffer("pyramid"),
|
|
485
|
+
});
|
|
403
486
|
const wg = k1.workgroupSize;
|
|
404
487
|
this.bound = {
|
|
405
488
|
n,
|
|
406
489
|
plan: plan1d(n, wg, caps),
|
|
407
490
|
fillPlan: plan1d(3 * n, wg, caps),
|
|
408
491
|
k1,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
:
|
|
492
|
+
// PD-14: on the exact tier K1's cellHist slot takes a dummy (partials, both read-only) and the block is
|
|
493
|
+
// dead under gridMax 0; hubCounters is the model's 16-byte buffer on every tier
|
|
494
|
+
k1Bound: k1.bind({
|
|
495
|
+
partials,
|
|
496
|
+
S: state,
|
|
497
|
+
T: trace,
|
|
498
|
+
cellHist: grid === null ? partials : resources.buffer("cellHist"),
|
|
499
|
+
hubCounters,
|
|
500
|
+
P: params,
|
|
501
|
+
}),
|
|
502
|
+
attraction,
|
|
414
503
|
k3,
|
|
415
|
-
k3Bound:
|
|
504
|
+
k3Bound:
|
|
505
|
+
k3?.bind({ pos, S: state, force, oldForce: velocity, fixedMask: fixed, partials, P: params }) ?? null,
|
|
506
|
+
grid,
|
|
416
507
|
k5,
|
|
417
508
|
k5Bound: k5.bind({ force, oldForce: velocity, fixedMask: fixed, S: state, pos, partials, P: params }),
|
|
418
509
|
toScene,
|
|
@@ -430,14 +521,19 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
430
521
|
* @returns the uniform values
|
|
431
522
|
*/
|
|
432
523
|
paramsFor(iteration: number, options: SpringElectricalOptions): UniformValues {
|
|
433
|
-
const { n } = this.requireResources();
|
|
524
|
+
const { n, core, tiers, tier, dim } = this.requireResources();
|
|
434
525
|
const resolved = resolveSpringElectricalOptions(options, this.current);
|
|
435
526
|
const { nearMax, extentFactor } = this.tuning;
|
|
527
|
+
const grid = tier === "grid" ? gridSpecFor(n, dim, this.tuning) : null;
|
|
528
|
+
// P4 PD-7: TIER 2 reads [0, hiEnd), TIER 1 [hiEnd, midEnd), TIER 0 [tierStart, tierEnd) = [midEnd, n)
|
|
529
|
+
const so = tiers?.segmentOffsets;
|
|
530
|
+
const hiEnd = so?.[1] ?? 0;
|
|
531
|
+
const midEnd = so?.[2] ?? 0;
|
|
436
532
|
return {
|
|
437
533
|
n,
|
|
438
534
|
dim: resolved.dim,
|
|
439
535
|
flags: 0,
|
|
440
|
-
tierStart:
|
|
536
|
+
tierStart: midEnd,
|
|
441
537
|
tierEnd: n,
|
|
442
538
|
iterationIndex: iteration,
|
|
443
539
|
seed: seedWord(resolved.seed),
|
|
@@ -449,9 +545,13 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
449
545
|
center: [resolved.center[0], resolved.center[1], resolved.center[2], 0],
|
|
450
546
|
settleThreshold: resolved.settleThreshold,
|
|
451
547
|
extentFactor,
|
|
452
|
-
gridMax: 0,
|
|
453
|
-
levels: 0,
|
|
454
|
-
|
|
548
|
+
gridMax: grid?.g ?? 0,
|
|
549
|
+
levels: grid?.levels ?? 0,
|
|
550
|
+
arcBase: 0,
|
|
551
|
+
arcEnd: arcCountOf(core),
|
|
552
|
+
accumulate: 0,
|
|
553
|
+
hiEnd,
|
|
554
|
+
midEnd,
|
|
455
555
|
frK: 0,
|
|
456
556
|
temperature: 0,
|
|
457
557
|
springLength: resolved.springLength,
|
|
@@ -463,23 +563,20 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
463
563
|
}
|
|
464
564
|
|
|
465
565
|
/**
|
|
466
|
-
* Records one iteration into the batch
|
|
467
|
-
*
|
|
468
|
-
*
|
|
566
|
+
* Records one iteration into the batch, stopping after stage `upTo` when given (PD-17: `upTo` names a position
|
|
567
|
+
* in the union list and the recording stops after the last stage recorded at or before it, so "K3" on the grid
|
|
568
|
+
* tier stops after K2 and "G5" on the exact tier after K3). The exact tier: K1, K2 (or the fill of force when
|
|
569
|
+
* arcCount === 0), K3, K5 in the batch's ONE compute pass, then toScene in a second pass that ends it. The grid
|
|
570
|
+
* tier (PD-16): the passes `se-k1` (K1), `se-attraction` (K2's tiers) and `se-grid` (G1-G7, K5) per iteration,
|
|
571
|
+
* then `se-to-scene`. With n === 0 nothing is recorded; a call before bind() completed is E_NOT_LOADED.
|
|
469
572
|
* @param batch - the batch being recorded
|
|
470
573
|
* @param slot - the UniformRing slot holding this iteration's Fa2Params
|
|
471
|
-
* @param tier -
|
|
574
|
+
* @param tier - the tier the simulation resolved at load() (the same rule bind() applied, PD-18)
|
|
472
575
|
* @param upTo - a stage name to stop after; undefined records every stage including toScene
|
|
473
576
|
*/
|
|
474
577
|
recordIteration(batch: CommandBatch, slot: number, tier: "exact" | "grid", upTo?: string): void {
|
|
475
|
-
if (tier === "grid") {
|
|
476
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", "the grid repulsion tier lands in P4", {
|
|
477
|
-
feature: "repulsion.grid",
|
|
478
|
-
hint: 'pass repulsion: "exact"',
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
578
|
const resources = this.requireResources();
|
|
482
|
-
const stop = upTo === undefined ?
|
|
579
|
+
const stop = upTo === undefined ? STAGE_TO_SCENE : this.stageIndex(upTo);
|
|
483
580
|
const { bound } = this;
|
|
484
581
|
if (bound === null) {
|
|
485
582
|
if (resources.n === 0) {
|
|
@@ -492,41 +589,110 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
492
589
|
);
|
|
493
590
|
}
|
|
494
591
|
const offset = resources.ring.offsetOf(slot);
|
|
592
|
+
if (tier === "grid") {
|
|
593
|
+
this.recordGridIteration(batch, bound, offset, stop);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const { k3, k3Bound } = bound;
|
|
597
|
+
if (k3 === null || k3Bound === null) {
|
|
598
|
+
throw new WebGpuGraphError("E_NOT_LOADED", "the spring-electrical model was bound on the grid tier", {
|
|
599
|
+
state: "loaded",
|
|
600
|
+
});
|
|
601
|
+
}
|
|
495
602
|
const pass = this.openPass !== null && this.openPass.id === batch.id ? this.openPass.pass : batch.pass("se");
|
|
496
603
|
this.openPass = { id: batch.id, pass };
|
|
497
604
|
bound.k1.dispatch(pass, bound.k1Bound, ONE_WORKGROUP, [offset]);
|
|
498
605
|
if (stop < 1) {
|
|
499
606
|
return;
|
|
500
607
|
}
|
|
501
|
-
|
|
502
|
-
bound.k2.dispatch(pass, bound.k2Bound, bound.plan, [offset]);
|
|
503
|
-
} else if (bound.fillForceBound !== null) {
|
|
504
|
-
bound.fill.dispatch(pass, bound.fillForceBound, bound.fillPlan, [0]);
|
|
505
|
-
}
|
|
608
|
+
this.recordK2(pass, bound, offset);
|
|
506
609
|
if (stop < 2) {
|
|
507
610
|
return;
|
|
508
611
|
}
|
|
509
|
-
|
|
510
|
-
if (stop <
|
|
612
|
+
k3.dispatch(pass, k3Bound, bound.plan, [offset]);
|
|
613
|
+
if (stop < STAGE_K5) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
bound.k5.dispatch(pass, bound.k5Bound, bound.plan, [offset]);
|
|
617
|
+
if (stop < STAGE_TO_SCENE) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.recordToScene(batch, bound, offset);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* The grid tier's iteration (PD-16): three compute passes before toScene; no K4.
|
|
625
|
+
* @param batch - the batch being recorded
|
|
626
|
+
* @param bound - the bound model
|
|
627
|
+
* @param offset - the Fa2Params dynamic offset of the iteration
|
|
628
|
+
* @param stop - the SE_STAGES index to stop after
|
|
629
|
+
*/
|
|
630
|
+
private recordGridIteration(batch: CommandBatch, bound: BoundModel, offset: number, stop: number): void {
|
|
631
|
+
const { grid } = bound;
|
|
632
|
+
if (grid === null) {
|
|
633
|
+
throw new WebGpuGraphError("E_NOT_LOADED", "the spring-electrical model was bound on the exact tier", {
|
|
634
|
+
state: "loaded",
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
this.openPass = null;
|
|
638
|
+
bound.k1.dispatch(batch.pass("se-k1"), bound.k1Bound, ONE_WORKGROUP, [offset]);
|
|
639
|
+
if (stop < 1) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
this.recordK2(batch.pass("se-attraction"), bound, offset);
|
|
643
|
+
if (stop < STAGE_G1) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const pass = batch.pass("se-grid");
|
|
647
|
+
const gridStop = stop < STAGE_K4 ? (SE_STAGES[stop] as GridStage) : undefined;
|
|
648
|
+
grid.recordRepulsion(pass, bound.n, offset, gridStop);
|
|
649
|
+
if (stop < STAGE_K5) {
|
|
511
650
|
return;
|
|
512
651
|
}
|
|
513
652
|
bound.k5.dispatch(pass, bound.k5Bound, bound.plan, [offset]);
|
|
514
|
-
if (stop <
|
|
653
|
+
if (stop < STAGE_TO_SCENE) {
|
|
515
654
|
return;
|
|
516
655
|
}
|
|
517
|
-
|
|
656
|
+
this.recordToScene(batch, bound, offset);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* K2's tier dispatches, or the fill of force when the graph has no arcs (spec 7.5).
|
|
661
|
+
* @param pass - the open compute pass
|
|
662
|
+
* @param bound - the bound model
|
|
663
|
+
* @param offset - the Fa2Params dynamic offset
|
|
664
|
+
*/
|
|
665
|
+
private recordK2(pass: GPUComputePassEncoder, bound: BoundModel, offset: number): void {
|
|
666
|
+
if (bound.attraction !== null) {
|
|
667
|
+
recordAttraction(pass, bound.attraction, offset);
|
|
668
|
+
} else if (bound.fillForceBound !== null) {
|
|
669
|
+
bound.fill.dispatch(pass, bound.fillForceBound, bound.fillPlan, [0]);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* The toScene pass that ends the iteration's pass; the batch is complete after it, so nothing reuses the pass.
|
|
675
|
+
* @param batch - the batch
|
|
676
|
+
* @param bound - the bound model
|
|
677
|
+
* @param offset - the Fa2Params dynamic offset
|
|
678
|
+
*/
|
|
679
|
+
private recordToScene(batch: CommandBatch, bound: BoundModel, offset: number): void {
|
|
518
680
|
this.openPass = null;
|
|
519
681
|
const scenePass = batch.pass("se-to-scene");
|
|
520
682
|
bound.toScene.dispatch(scenePass, bound.toSceneBound, bound.plan, [offset]);
|
|
521
683
|
}
|
|
522
684
|
|
|
523
685
|
/**
|
|
524
|
-
* kineticEnergy = 0 and temperature = 0 in the header (the velocities start at 0 through the buffer's `zero`)
|
|
686
|
+
* kineticEnergy = 0 and temperature = 0 in the header (the velocities start at 0 through the buffer's `zero`);
|
|
687
|
+
* on a grid load the frame of the first build (K1 folds nothing on the first iteration).
|
|
525
688
|
* @param state - the state writer of the simulation
|
|
526
689
|
*/
|
|
527
690
|
onLoad(state: StateWriter): void {
|
|
528
691
|
state.set("kineticEnergy", 0);
|
|
529
692
|
state.set("temperature", 0);
|
|
693
|
+
if (this.nextGrid !== null) {
|
|
694
|
+
writeGridFrame(state, this.nextGrid, this.tuning.extentFactor);
|
|
695
|
+
}
|
|
530
696
|
}
|
|
531
697
|
|
|
532
698
|
/**
|
|
@@ -548,9 +714,10 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
548
714
|
}
|
|
549
715
|
|
|
550
716
|
/**
|
|
551
|
-
* Decodes the state header and the k trace records of a completed batch into SpringElectricalStats:
|
|
552
|
-
* tier
|
|
553
|
-
*
|
|
717
|
+
* Decodes the state header and the k trace records of a completed batch into SpringElectricalStats:
|
|
718
|
+
* `repulsionTier` is the bound tier, the grid fields are the header's on the grid tier and null on the exact
|
|
719
|
+
* tier; msPerIteration null (the simulation owns the clock); `kineticEnergy` is the last folded value (one
|
|
720
|
+
* iteration behind the last integrate, PD-4).
|
|
554
721
|
* @param state - a DataView over the 256-byte state header
|
|
555
722
|
* @param trace - a DataView over the k Fa2Trace records of the batch
|
|
556
723
|
* @returns the stats
|
|
@@ -568,15 +735,16 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
568
735
|
settledCount: scalar(record, "settledCount"),
|
|
569
736
|
});
|
|
570
737
|
}
|
|
738
|
+
const grid = this.resources?.tier === "grid";
|
|
571
739
|
return {
|
|
572
740
|
iteration: scalar(header, "iteration"),
|
|
573
741
|
meanDisplacement: scalar(header, "meanDisplacement"),
|
|
574
742
|
rmsRadius: scalar(header, "rmsRadius"),
|
|
575
743
|
layoutRadius: scalar(header, "radius"),
|
|
576
744
|
centroid: [centroid[0], centroid[1], centroid[2]],
|
|
577
|
-
repulsionTier: "exact",
|
|
578
|
-
maxCellOccupancy: null,
|
|
579
|
-
outsideGrid: null,
|
|
745
|
+
repulsionTier: grid ? "grid" : "exact",
|
|
746
|
+
maxCellOccupancy: grid ? scalar(header, "maxCellOccupancy") : null,
|
|
747
|
+
outsideGrid: grid ? scalar(header, "outsideGrid") : null,
|
|
580
748
|
msPerIteration: null,
|
|
581
749
|
kineticEnergy: scalar(header, "kineticEnergy"),
|
|
582
750
|
trace: records,
|
|
@@ -612,16 +780,26 @@ export class SpringElectricalModel implements ForceModel<SpringElectricalOptions
|
|
|
612
780
|
throw invalid("upTo", upTo, SE_STAGES.join(" | "));
|
|
613
781
|
}
|
|
614
782
|
|
|
615
|
-
/**
|
|
783
|
+
/** Releases the grid stage's lease and the bind groups (the simulation calls it from dispose() once every in-flight batch has settled). */
|
|
784
|
+
dispose(): void {
|
|
785
|
+
this.dropBound();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** Drops the bind groups of the previous bind() (the buffers changed), releases the grid stage's lease and forgets the pass of a batch recorded before the rebind. */
|
|
616
789
|
private dropBound(): void {
|
|
617
790
|
this.openPass = null;
|
|
618
791
|
const { bound } = this;
|
|
619
792
|
if (bound === null) {
|
|
620
793
|
return;
|
|
621
794
|
}
|
|
622
|
-
for (const kernel of [bound.k1, bound.
|
|
795
|
+
for (const kernel of [bound.k1, bound.k5, bound.toScene, bound.fill]) {
|
|
796
|
+
kernel.invalidate();
|
|
797
|
+
}
|
|
798
|
+
bound.k3?.invalidate();
|
|
799
|
+
for (const [kernel] of bound.attraction?.kernels ?? []) {
|
|
623
800
|
kernel.invalidate();
|
|
624
801
|
}
|
|
802
|
+
bound.grid?.dispose();
|
|
625
803
|
this.bound = null;
|
|
626
804
|
}
|
|
627
805
|
}
|
|
@@ -643,7 +821,8 @@ function resolvePatch(
|
|
|
643
821
|
|
|
644
822
|
/**
|
|
645
823
|
* Spec 3.3 createSpringElectrical, verbatim: a GpuLayoutSimulation running ngraph's spring-electrical model on the
|
|
646
|
-
* exact repulsion tier with ngraph's defaults (spec 7.20) and the GPU-only tuning of
|
|
824
|
+
* exact or the grid repulsion tier (spec 7.8) with ngraph's defaults (spec 7.20) and the GPU-only tuning of
|
|
825
|
+
* GpuLayoutTuning.
|
|
647
826
|
* @param ctx - the context (E_DISPOSED / E_DEVICE_LOST through assertReady)
|
|
648
827
|
* @param options - the spring-electrical options and the GPU-only tuning knobs in one record
|
|
649
828
|
* @returns the simulation in state "created"; load() next
|