@voluma/vlam 0.3.4 → 0.4.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.
@@ -1,5 +1,6 @@
1
1
  import * as THREE from 'three/webgpu';
2
2
  import type { SplatSorter } from './sorter';
3
+ import type { SplatSortMetric } from './splat-mesh-types';
3
4
  import type { uniformArray } from 'three/tsl';
4
5
  /**
5
6
  * Optional per-source world transform for a unified {@link MergedSplatMesh} pool.
@@ -78,7 +79,9 @@ export declare class ComputeSorter implements SplatSorter {
78
79
  private readonly mirrors;
79
80
  /** Set by {@link dispose}; makes a second dispose a no-op. */
80
81
  private disposed;
81
- /** Row 2 of the model-view matrix; view-space z = row2 · (position, 1). */
82
+ /** Rows of the model-view matrix used by the selected sort metric. */
83
+ private readonly viewRow0;
84
+ private readonly viewRow1;
82
85
  private readonly viewRow2;
83
86
  private readonly depthMin;
84
87
  private readonly depthScale;
@@ -87,6 +90,7 @@ export declare class ComputeSorter implements SplatSorter {
87
90
  /** Highest bucket index this sort uses; see {@link effectiveBucketCount}. */
88
91
  private readonly bucketMax;
89
92
  private readonly viewCenter;
93
+ private readonly sortMetric;
90
94
  constructor(options: {
91
95
  renderer: THREE.WebGPURenderer;
92
96
  /** Pool capacity in splats; all buffers are sized once from this. */
@@ -103,6 +107,8 @@ export declare class ComputeSorter implements SplatSorter {
103
107
  sourceIndexAttribute: THREE.StorageBufferAttribute;
104
108
  /** Per-source world transform for a unified pool; omit for a single mesh. */
105
109
  perSource?: PerSourceSortTransform;
110
+ /** Camera-space ordering key. Default `'depth'`. */
111
+ sortMetric?: SplatSortMetric;
106
112
  });
107
113
  /**
108
114
  * Buckets to actually use for `activeCount` splats, rounded up to a power of
@@ -25,7 +25,7 @@
25
25
  *
26
26
  * @module core
27
27
  */
28
- export { SplatMesh, MAX_SH_BANDS, DEFAULT_FOVEATION_TARGET_PX, DEFAULT_FOVEATION_DRAW_BUDGET, resolveSplatPerformanceProfile, type UnifiedSourceView, type SplatMeshOptions, type SplatUpdateOptions, type SplatSortStrategy, type SplatPerformanceProfile, type SplatFoveationMode, type SplatRange, type SplatPickOptions, type SplatPickResult, type SplatNearestResult, type SplatRayResult, type SplatHeightResult, type SplatChannelType, type SplatChannelOptions, } from './splat-mesh';
28
+ export { SplatMesh, MAX_SH_BANDS, DEFAULT_FOVEATION_TARGET_PX, DEFAULT_FOVEATION_DRAW_BUDGET, resolveSplatPerformanceProfile, type UnifiedSourceView, type SplatMeshOptions, type SplatUpdateOptions, type SplatSortStrategy, type SplatSortMetric, type SplatPerformanceProfile, type SplatFoveationMode, type SplatRange, type SplatPickOptions, type SplatPickResult, type SplatNearestResult, type SplatRayResult, type SplatHeightResult, type SplatChannelType, type SplatChannelOptions, } from './splat-mesh';
29
29
  export { SplatPool, type SplatPoolOptions } from './splat-mesh-pool';
30
30
  export { SPLAT_DATA_TEXTURE_WIDTH, deviceMaxTextureSize, assertPoolRowsFitDevice, } from './splat-mesh-pool';
31
31
  export { MAX_DOF_RADIUS_PX, MAX_DOF_VARIANCE, apertureAngleFromSize, computeDofCocVariancePx2, computeDofOpacityFade, clampDepthOfFieldSettings, type DepthOfFieldSettings, } from './depth-of-field';
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import * as THREE from 'three/webgpu';
6
6
  import type { SplatSorter } from './sorter';
7
+ import type { SplatSortMetric } from './splat-mesh-types';
7
8
  /**
8
9
  * Stable four-bit-per-pass WebGPU radix sorter.
9
10
  *
@@ -22,6 +23,8 @@ export declare class RadixSorter implements SplatSorter {
22
23
  private readonly mirrors;
23
24
  /** Set by {@link dispose}; makes a second dispose a no-op. */
24
25
  private disposed;
26
+ private readonly viewRow0;
27
+ private readonly viewRow1;
25
28
  private readonly viewRow2;
26
29
  private readonly depthMin;
27
30
  private readonly depthScale;
@@ -29,6 +32,7 @@ export declare class RadixSorter implements SplatSorter {
29
32
  private readonly viewCenter;
30
33
  /** Exact mode avoids scene-bounds quantization entirely. */
31
34
  private readonly exactDepth;
35
+ private readonly sortMetric;
32
36
  constructor(options: {
33
37
  renderer: THREE.WebGPURenderer;
34
38
  capacity: number;
@@ -40,6 +44,8 @@ export declare class RadixSorter implements SplatSorter {
40
44
  sourceIndexAttribute: THREE.StorageBufferAttribute;
41
45
  /** Keep every Float32 depth bit instead of quantizing to 24 bits. */
42
46
  exactDepth?: boolean;
47
+ /** Camera-space ordering key. Default `'depth'`. */
48
+ sortMetric?: SplatSortMetric;
43
49
  });
44
50
  sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean;
45
51
  dispose(): void;
@@ -21,6 +21,8 @@ export interface WriteMessage {
21
21
  }
22
22
  export interface SortMessage {
23
23
  type: 'sort';
24
+ /** Camera-space ordering key. */
25
+ sortMetric: 'depth' | 'radial';
24
26
  /** Column-major 4×4 model-view matrix elements. */
25
27
  modelView: Float32Array;
26
28
  /** Active ranges as (start, count) pairs of pool splat indices, in
@@ -5,10 +5,10 @@ import type * as THREE from 'three/webgpu';
5
5
  *
6
6
  * - `ComputeSorter`: GPU counting sort in TSL compute (WebGPU backend);
7
7
  * supports dynamic-capacity meshes via source-index indirection.
8
- * - `WorkerSorter`: CPU counting sort in a Web Worker (WebGL2 fallback,
9
- * which has no compute shaders); handles static and dynamic-capacity
10
- * (streamed) meshes alike by mirroring the pool's centers and sorting
11
- * the active spans.
8
+ * - `WorkerSorter`: stable CPU radix sort in a Web Worker (the WebGL2
9
+ * fallback and an explicit WebGPU stability option); handles static and
10
+ * dynamic-capacity meshes alike by mirroring the pool's centers and
11
+ * sorting the active spans.
12
12
  * - `RadixSorter`: experimental GPU radix (opt-in via `sortStrategy: 'radix'`).
13
13
  */
14
14
  export type SplatSorterKind = 'counting' | 'radix' | 'worker';
@@ -66,18 +66,33 @@ export interface SplatMeshOptions {
66
66
  /**
67
67
  * Minimum interval between WebGPU sorts while the camera moves. When
68
68
  * omitted, the interval adapts to the active splat count. Use `0` to sort
69
- * every changed frame. WebGL worker sorting is unaffected.
69
+ * every changed frame. WebGL worker sorting is unaffected; a worker selected
70
+ * explicitly on WebGPU still observes this submission cadence.
70
71
  */
71
72
  sortIntervalMs?: number;
72
73
  /**
73
74
  * WebGPU sorter used for A/B validation. Defaults to the proven counting
74
- * sorter. `'radix'` keeps the fast 24-bit key path; `'exact'` lazy-loads a
75
- * stable 32-bit Float32-depth radix path that avoids scene-range
76
- * quantization. The first frames may skip sorting until the module resolves.
75
+ * sorter. `'worker'` keeps rendering on WebGPU but sorts asynchronously in
76
+ * the stable CPU worker also used by the WebGL fallback; this is useful for
77
+ * Spark-like temporal stability on captures with extreme position outliers.
78
+ * `'radix'` keeps the fast 24-bit GPU key path; `'exact'` lazy-loads a 32-bit
79
+ * Float32-depth GPU radix path. The first frames may skip GPU radix sorting
80
+ * until the module resolves.
77
81
  *
78
82
  * @experimental Radix strategies may change in a minor release.
79
83
  */
80
84
  sortStrategy?: SplatSortStrategy;
85
+ /**
86
+ * Geometric key used to order transparent splats back-to-front.
87
+ *
88
+ * `'depth'` (default) sorts along the camera's view axis and gives the most
89
+ * accurate alpha order for a fixed view. `'radial'` sorts by distance from
90
+ * the camera, matching Spark's stable default: camera rotation alone leaves
91
+ * the order unchanged, which greatly reduces whole-scene shimmer while
92
+ * orbiting dense captures. Pair it with `sortStrategy: 'exact'` when extreme
93
+ * position outliers would make quantized buckets too coarse.
94
+ */
95
+ sortMetric?: SplatSortMetric;
81
96
  /**
82
97
  * Render-quality policy. `smooth` rejects negligible projected contributions.
83
98
  *
@@ -272,7 +287,9 @@ export interface SplatMeshOptions {
272
287
  maxTextureSize?: number;
273
288
  }
274
289
  /** Available WebGPU depth-sort implementations. */
275
- export type SplatSortStrategy = 'counting' | 'radix' | 'exact';
290
+ export type SplatSortStrategy = 'counting' | 'worker' | 'radix' | 'exact';
291
+ /** Camera-space key used for back-to-front splat ordering. */
292
+ export type SplatSortMetric = 'depth' | 'radial';
276
293
  /** Controls optional work during a per-frame source update. */
277
294
  export interface SplatUpdateOptions {
278
295
  /** Leave sorting to {@link UnifiedSplatMesh}; uploads and LOD state still update. */
@@ -194,8 +194,10 @@ export declare class SplatMesh extends THREE.Mesh implements SplatPoolTenant {
194
194
  * storage-attribute GPU buffers that never sat in a geometry. */
195
195
  private lastRenderer;
196
196
  private readonly currentModelView;
197
+ /** Pose components that can actually change the selected sort key. */
198
+ private readonly currentSortState;
197
199
  /** Initialized to an impossible matrix so the first frame always sorts. */
198
- private readonly lastSortedModelView;
200
+ private readonly lastSortedState;
199
201
  private readonly sortScheduler;
200
202
  /** One-frame queue-headroom hint used before a staged atomic commit. */
201
203
  private deferSortRequestOnce;
@@ -204,6 +206,7 @@ export declare class SplatMesh extends THREE.Mesh implements SplatPoolTenant {
204
206
  /** The active list the current depth order was built from. */
205
207
  private sortedActiveListVersion;
206
208
  private readonly sortStrategy;
209
+ private readonly sortMetric;
207
210
  /** Resolved only when `sortStrategy === 'radix'`; see {@link ensureRadixSorter}. */
208
211
  private RadixSorterCtor;
209
212
  private radixSorterLoad;
@@ -604,7 +607,7 @@ export declare class SplatMesh extends THREE.Mesh implements SplatPoolTenant {
604
607
  renderView(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, target?: THREE.RenderTarget | null): void;
605
608
  /**
606
609
  * Sorts the shared order buffer for a secondary view's camera, bypassing the
607
- * primary view's sort scheduler and its `lastSortedModelView` record (that
610
+ * primary view's sort scheduler and its last-sorted pose record (that
608
611
  * state belongs to `update()`'s camera). WebGPU dispatches synchronously into
609
612
  * the render queue, so the following draw reads this order.
610
613
  */
@@ -722,6 +725,12 @@ export declare class SplatMesh extends THREE.Mesh implements SplatPoolTenant {
722
725
  * order then changes without any camera motion.
723
726
  */
724
727
  protected invalidateSort(): void;
728
+ /**
729
+ * Writes the camera/mesh state that can change the selected ordering key.
730
+ * Radial distance is invariant under camera rotation, so its state contains
731
+ * the mesh's world linear transform and camera-relative translation only.
732
+ */
733
+ private writeSortState;
725
734
  private createSorter;
726
735
  /** Prefetches the experimental radix sorter; safe to call repeatedly. */
727
736
  private ensureRadixSorter;
@@ -9,3 +9,20 @@ import type * as THREE from 'three/webgpu';
9
9
  * compose into a shear.
10
10
  */
11
11
  export declare function viewDepthRadius(modelView: THREE.Matrix4, radius: number): number;
12
+ /**
13
+ * A conservative camera-space radius for radial-distance quantization.
14
+ *
15
+ * Uses `sqrt(||A^T A||∞)`, an upper bound on the linear part's largest
16
+ * singular value. Unlike `Matrix4.getMaxScaleOnAxis()`, it cannot under-bound
17
+ * a sphere when a transformed hierarchy introduces shear.
18
+ */
19
+ export declare function viewRadialRadius(modelView: THREE.Matrix4, radius: number): number;
20
+ /**
21
+ * Builds the pose signature relevant to radial sorting.
22
+ *
23
+ * World-space distance depends on the mesh's linear transform and its
24
+ * translation relative to the camera, but not on camera orientation. The
25
+ * scheduler compares this matrix so rotating in place does not dispatch an
26
+ * identical radial sort.
27
+ */
28
+ export declare function radialSortState(meshWorld: THREE.Matrix4, cameraWorld: THREE.Matrix4, out: THREE.Matrix4): THREE.Matrix4;
@@ -1,10 +1,11 @@
1
1
  import type * as THREE from 'three/webgpu';
2
2
  import type { SplatSorter } from './sorter';
3
+ import type { SplatSortMetric } from './splat-mesh-types';
3
4
  /**
4
- * CPU depth sorter: counting sort in a Web Worker. Used on the WebGL2
5
- * fallback backend, which has no compute shaders. Works for static and
6
- * dynamic-capacity (streamed) meshes alike: the worker keeps a mirror of
7
- * the pool's centers, and each sort covers only the active pool spans.
5
+ * Stable CPU radix sorter in a Web Worker. Used by the WebGL2 fallback and,
6
+ * when explicitly selected, alongside WebGPU rendering for Spark-like sort
7
+ * cadence. Works for static and dynamic-capacity meshes alike: the worker
8
+ * keeps a mirror of the pool's centers and sorts only the active spans.
8
9
  *
9
10
  * One sort runs at a time; requests that arrive while the worker is busy
10
11
  * are declined so the caller retries with the then-current camera on a
@@ -15,6 +16,7 @@ export declare class WorkerSorter implements SplatSorter {
15
16
  private readonly worker;
16
17
  private readonly splatIndexAttribute;
17
18
  private readonly host;
19
+ private readonly sortMetric;
18
20
  private inFlight;
19
21
  /** Set by {@link dispose}; drops any already-delivered order message. */
20
22
  private disposed;
@@ -25,7 +27,7 @@ export declare class WorkerSorter implements SplatSorter {
25
27
  private lastSubmittedAt;
26
28
  private lastCompletedAt;
27
29
  private lastLatencyMs;
28
- constructor(host: WorkerSorterHost);
30
+ constructor(host: WorkerSorterHost, sortMetric?: SplatSortMetric);
29
31
  sort(modelView: THREE.Matrix4, _activeCount: number, _bounds: THREE.Sphere): boolean;
30
32
  /** Internal timing diagnostics used by the demo's opt-in XR A/B harness. */
31
33
  snapshot(): {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  var A = Object.defineProperty;
2
2
  var y = (i, t, e) => t in i ? A(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
3
3
  var l = (i, t, e) => y(i, typeof t != "symbol" ? t + "" : t, e);
4
- import { S as _, y as T } from "./splat-mesh-CsLOQb08.js";
5
- import { D as k, a as z, c as $ } from "./splat-mesh-CsLOQb08.js";
6
- import { D as K, a as Y, b as Z, c as J, M as Q, d as ee, e as te, f as re, g as se, h as oe, i as ae, j as ie, k as ne, l as ce, m as le, n as ue, x as pe } from "./relighting-Tiwep8yd.js";
4
+ import { S as _, y as T } from "./splat-mesh-B1KlWHvx.js";
5
+ import { D as k, a as z, c as $ } from "./splat-mesh-B1KlWHvx.js";
6
+ import { D as K, b as Y, c as Z, d as J, M as Q, e as ee, f as te, g as re, h as se, i as oe, j as ae, k as ie, l as ne, m as ce, n as le, o as ue, x as pe } from "./relighting-qaANKC4Z.js";
7
7
  import * as p from "three/webgpu";
8
8
  import { WebGPURenderer as D } from "three/webgpu";
9
9
  import { uniformArray as v } from "three/tsl";
@@ -0,0 +1,181 @@
1
+ var Ut = Object.defineProperty;
2
+ var Dt = (d, s, e) => s in d ? Ut(d, s, { enumerable: !0, configurable: !0, writable: !0, value: e }) : d[s] = e;
3
+ var r = (d, s, e) => Dt(d, typeof s != "symbol" ? s + "" : s, e);
4
+ import * as h from "three/webgpu";
5
+ import { uniform as B, storage as w, Fn as A, If as m, float as O, instanceIndex as o, ivec2 as Ot, int as q, textureLoad as Vt, floatBitsToUint as Kt, uint as t, atomicStore as V, workgroupId as et, Loop as I, invocationLocalIndex as Rt, atomicAdd as Lt, atomicLoad as _, workgroupArray as Pt, storageBarrier as H, workgroupBarrier as Y, atomicOr as Wt, countOneBits as st } from "three/tsl";
6
+ import { S as zt, v as Xt, a as Nt, r as Gt } from "./relighting-qaANKC4Z.js";
7
+ const bt = 24, qt = 32, it = 4, xt = (1 << bt) - 1;
8
+ function Ht(d) {
9
+ if (!Number.isInteger(d) || d <= 0 || d % it !== 0)
10
+ throw new RangeError("Radix key width must be a positive multiple of four bits.");
11
+ return d / it;
12
+ }
13
+ const u = 256, rt = 8, nt = u * rt, K = 16, E = u / 32, Z = K * E, F = 256;
14
+ class Jt {
15
+ constructor(s) {
16
+ r(this, "kind", "radix");
17
+ r(this, "renderer");
18
+ r(this, "stages", []);
19
+ r(this, "workingAttributes");
20
+ /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */
21
+ r(this, "mirrors");
22
+ /** Set by {@link dispose}; makes a second dispose a no-op. */
23
+ r(this, "disposed", !1);
24
+ r(this, "viewRow0", B(new h.Vector4()));
25
+ r(this, "viewRow1", B(new h.Vector4()));
26
+ r(this, "viewRow2", B(new h.Vector4()));
27
+ r(this, "depthMin", B(0));
28
+ r(this, "depthScale", B(0));
29
+ r(this, "activeCount", B(0));
30
+ r(this, "viewCenter", new h.Vector3());
31
+ /** Exact mode avoids scene-bounds quantization entirely. */
32
+ r(this, "exactDepth");
33
+ r(this, "sortMetric");
34
+ const { capacity: e, centersTexture: g, dataTextureWidth: i, centersBuffer: p } = s;
35
+ if (!g && !p)
36
+ throw new Error("RadixSorter: provide centersTexture or centersBuffer.");
37
+ if (g && i === void 0)
38
+ throw new Error("RadixSorter: dataTextureWidth is required with centersTexture.");
39
+ this.renderer = s.renderer, this.sortMetric = s.sortMetric ?? "depth";
40
+ const S = s.exactDepth === !0;
41
+ this.exactDepth = S;
42
+ const j = Ht(S ? qt : bt), f = Math.ceil(e / nt), k = K * f, L = Math.ceil(k / F), ot = new h.StorageBufferAttribute(new Uint32Array(e), 1), at = new h.StorageBufferAttribute(new Uint32Array(e), 1), ct = new h.StorageBufferAttribute(new Uint32Array(e), 1), ut = new h.StorageBufferAttribute(new Uint32Array(e), 1), dt = new h.StorageBufferAttribute(
43
+ new Uint32Array(k),
44
+ 1
45
+ ), lt = new h.StorageBufferAttribute(new Uint32Array(L), 1), ht = new h.StorageBufferAttribute(
46
+ new Uint32Array(f * Z),
47
+ 1
48
+ );
49
+ this.workingAttributes = [
50
+ ot,
51
+ at,
52
+ ct,
53
+ ut,
54
+ dt,
55
+ lt,
56
+ ht
57
+ ], this.mirrors = new zt(this.workingAttributes);
58
+ const P = w(ot, "uint", e), mt = w(at, "uint", e), W = w(ct, "uint", e), wt = w(ut, "uint", e), St = w(s.sourceIndexAttribute, "uint", e), kt = w(s.splatIndexAttribute, "float", e), gt = p ? w(p, "vec4", e) : null, v = w(dt, "uint", k).toAtomic(), z = w(lt, "uint", L), T = w(ht, "uint", f * Z).toAtomic(), Tt = A(() => {
59
+ m(O(o).lessThan(this.activeCount), () => {
60
+ const a = St.element(o), X = g ? Ot(
61
+ q(a).mod(q(i)),
62
+ q(a).div(q(i))
63
+ ) : null, y = gt ? gt.element(a).xyz : Vt(g, X).xyz, N = this.viewRow2.xyz.dot(y).add(this.viewRow2.w), G = this.sortMetric === "radial" ? this.viewRow0.xyz.dot(y).add(this.viewRow0.w).pow2().add(this.viewRow1.xyz.dot(y).add(this.viewRow1.w).pow2()).add(N.pow2()).sqrt().negate() : N;
64
+ if (S) {
65
+ const M = Kt(G), J = M.shiftRight(t(31)).equal(t(0)).select(t(2147483648), t(4294967295));
66
+ P.element(o).assign(M.bitXor(J));
67
+ } else
68
+ P.element(o).assign(G.sub(this.depthMin).mul(this.depthScale).clamp(0, xt).toUint());
69
+ W.element(o).assign(a);
70
+ });
71
+ })().compute(e, [u]);
72
+ this.stages.push(Tt);
73
+ for (let a = 0; a < j; a++) {
74
+ const X = a % 2 === 0 ? P : mt, y = a % 2 === 0 ? mt : P, N = a % 2 === 0 ? W : wt, G = a % 2 === 0 ? wt : W, M = t(a * it), J = A(() => {
75
+ V(v.element(o), t(0));
76
+ })().compute(k, [u]), It = A(() => {
77
+ const n = et.x.mul(t(nt));
78
+ I(rt, ({ i: c }) => {
79
+ const l = n.add(t(c).mul(t(u))).add(Rt);
80
+ m(O(l).lessThan(this.activeCount), () => {
81
+ const R = X.element(l).shiftRight(M).bitAnd(t(15));
82
+ Lt(v.element(R.mul(t(f)).add(et.x)), t(1));
83
+ });
84
+ });
85
+ })().compute(f * u, [u]), _t = A(() => {
86
+ const n = o.mul(t(F)), c = t(0).toVar();
87
+ I(F, ({ i: l }) => {
88
+ const R = n.add(t(l));
89
+ m(R.lessThan(t(k)), () => {
90
+ const C = _(v.element(R)).toVar();
91
+ V(v.element(R), c), c.addAssign(C);
92
+ });
93
+ }), z.element(o).assign(c);
94
+ })().compute(L, [64]), Et = A(() => {
95
+ m(o.equal(t(0)), () => {
96
+ const n = t(0).toVar();
97
+ I(L, ({ i: c }) => {
98
+ const l = z.element(c).toVar();
99
+ z.element(c).assign(n), n.addAssign(l);
100
+ });
101
+ });
102
+ })().compute(1, [64]), yt = A(() => {
103
+ const n = o.div(t(F));
104
+ V(
105
+ v.element(o),
106
+ _(v.element(o)).add(z.element(n))
107
+ );
108
+ })().compute(k, [u]), Q = Pt("uint", K), Mt = A(() => {
109
+ const n = Rt, c = et.x, l = c.mul(t(Z));
110
+ m(n.lessThan(t(K)), () => {
111
+ Q.element(n).assign(t(0));
112
+ }), m(n.lessThan(t(Z)), () => {
113
+ V(T.element(l.add(n)), t(0));
114
+ }), H(), Y(), I(rt, ({ i: R }) => {
115
+ const C = c.mul(t(nt)).add(R.toUint().mul(t(u))).add(n), pt = O(C).lessThan(this.activeCount), $ = t(0).toVar(), At = t(0).toVar(), U = t(0).toVar();
116
+ m(pt, () => {
117
+ $.assign(X.element(C)), At.assign(N.element(C)), U.assign($.shiftRight(M).bitAnd(t(15)));
118
+ const x = n.shiftRight(t(5)), D = n.bitAnd(t(31)), b = l.add(U.mul(t(E))).add(x);
119
+ Wt(T.element(b), t(1).shiftLeft(D));
120
+ }), H(), Y(), m(pt, () => {
121
+ const x = n.shiftRight(t(5)), D = n.bitAnd(t(31)), b = l.add(U.mul(t(E))), tt = Q.element(U).toVar();
122
+ I(E, ({ i: vt }) => {
123
+ m(t(vt).lessThan(x), () => {
124
+ tt.addAssign(
125
+ st(_(T.element(b.add(vt.toUint()))))
126
+ );
127
+ });
128
+ });
129
+ const Ct = t(1).shiftLeft(D).sub(t(1));
130
+ tt.addAssign(
131
+ st(_(T.element(b.add(x))).bitAnd(Ct))
132
+ );
133
+ const ft = _(v.element(U.mul(t(f)).add(c))).add(tt);
134
+ y.element(ft).assign($), G.element(ft).assign(At);
135
+ }), H(), Y(), m(n.lessThan(t(K)), () => {
136
+ const x = t(0).toVar();
137
+ I(E, ({ i: D }) => {
138
+ const b = l.add(n.mul(t(E))).add(D.toUint());
139
+ x.addAssign(st(_(T.element(b)))), V(T.element(b), t(0));
140
+ }), Q.element(n).addAssign(x);
141
+ }), H(), Y();
142
+ });
143
+ })().compute(f * u, [u]);
144
+ this.stages.push(
145
+ J,
146
+ It,
147
+ _t,
148
+ Et,
149
+ yt,
150
+ Mt
151
+ );
152
+ }
153
+ const Bt = A(() => {
154
+ m(O(o).lessThan(this.activeCount), () => {
155
+ kt.element(o).assign(O(W.element(o)));
156
+ });
157
+ })().compute(e, [u]);
158
+ this.stages.push(Bt);
159
+ }
160
+ sort(s, e, g) {
161
+ if (e === 0) return !0;
162
+ const i = s.elements;
163
+ if (this.viewRow0.value.set(i[0], i[4], i[8], i[12]), this.viewRow1.value.set(i[1], i[5], i[9], i[13]), this.viewRow2.value.set(i[2], i[6], i[10], i[14]), this.activeCount.value = e, !this.exactDepth) {
164
+ this.viewCenter.copy(g.center).applyMatrix4(s);
165
+ const p = this.sortMetric === "radial" ? Xt(s, g.radius) : Nt(s, g.radius), S = this.sortMetric === "radial" ? -(this.viewCenter.length() + p) : this.viewCenter.z - p, j = this.sortMetric === "radial" ? -Math.max(0, this.viewCenter.length() - p) : this.viewCenter.z + p;
166
+ this.depthMin.value = S, this.depthScale.value = xt / (j - S || 1);
167
+ }
168
+ return this.stages[0].count = e, this.stages[this.stages.length - 1].count = e, this.renderer.compute(this.stages), this.mirrors.settled || this.mirrors.release(this.renderer), !0;
169
+ }
170
+ dispose() {
171
+ if (!this.disposed) {
172
+ this.disposed = !0;
173
+ for (const s of this.stages) s.dispose();
174
+ Gt(this.renderer, this.workingAttributes);
175
+ }
176
+ }
177
+ }
178
+ export {
179
+ Jt as RadixSorter
180
+ };
181
+ //# sourceMappingURL=radix-sorter-tSffmerk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radix-sorter-tSffmerk.js","sources":["../src/lib/core/radix-sort.ts","../src/lib/core/radix-sorter.ts"],"sourcesContent":["/**\n * @role Utility\n * Shared constants and deterministic reference logic for GPU radix sorting.\n */\n\n/** Depth precision retained by both the GPU radix path and WebGL worker path. */\nexport const RADIX_KEY_BITS = 24;\n/** Exact Float32 depth keys use every sortable IEEE-754 bit. */\nexport const RADIX_EXACT_KEY_BITS = 32;\n/** Four-bit digits keep the portable shader's workgroup memory requirements small. */\nexport const RADIX_BITS_PER_PASS = 4;\nexport const RADIX_BUCKETS = 1 << RADIX_BITS_PER_PASS;\nexport const RADIX_PASS_COUNT = RADIX_KEY_BITS / RADIX_BITS_PER_PASS;\nexport const RADIX_KEY_MAX = (1 << RADIX_KEY_BITS) - 1;\n\n/** Number of four-bit passes required for one unsigned radix key width. */\nexport function radixPassCount(keyBits: number): number {\n if (!Number.isInteger(keyBits) || keyBits <= 0 || keyBits % RADIX_BITS_PER_PASS !== 0) {\n throw new RangeError('Radix key width must be a positive multiple of four bits.');\n }\n return keyBits / RADIX_BITS_PER_PASS;\n}\n\n/**\n * Maps a Float32 bit pattern onto unsigned integer order without losing depth\n * precision. Ascending mapped keys are ascending numeric floats, including\n * negative view-space Z values (farther splats first in VLAM's convention).\n */\nexport function sortableFloat32Bits(bits: number): number {\n const value = bits >>> 0;\n return (value ^ ((value & 0x80000000) === 0 ? 0x80000000 : 0xffffffff)) >>> 0;\n}\n\n/** Quantizes a depth into the unsigned 24-bit key used by the GPU sorter. */\nexport function quantizeDepthKey(depth: number, minimum: number, maximum: number): number {\n const span = maximum - minimum;\n if (!Number.isFinite(depth) || !Number.isFinite(span) || span <= 0) return 0;\n return Math.min(\n RADIX_KEY_MAX,\n Math.max(0, Math.round(((depth - minimum) / span) * RADIX_KEY_MAX)),\n );\n}\n\n/**\n * Stable CPU reference for validating the GPU's six four-bit radix passes.\n * This is deliberately test/debug-only logic, not the WebGL production sorter.\n */\nexport function stableRadixSortReference(\n keys: Uint32Array,\n values?: Uint32Array,\n keyBits = RADIX_KEY_BITS,\n): Uint32Array {\n const count = keys.length;\n let inputKeys = Uint32Array.from(keys);\n let outputKeys = new Uint32Array(count);\n let inputValues = values ? Uint32Array.from(values) : Uint32Array.from(keys.keys());\n let outputValues = new Uint32Array(count);\n const counts = new Uint32Array(RADIX_BUCKETS);\n const offsets = new Uint32Array(RADIX_BUCKETS);\n\n for (let pass = 0; pass < radixPassCount(keyBits); pass++) {\n counts.fill(0);\n const shift = pass * RADIX_BITS_PER_PASS;\n for (let i = 0; i < count; i++) {\n const digit = ((inputKeys[i] as number) >>> shift) & 0xf;\n counts[digit] = (counts[digit] as number) + 1;\n }\n let total = 0;\n for (let digit = 0; digit < RADIX_BUCKETS; digit++) {\n offsets[digit] = total;\n total += counts[digit] as number;\n }\n for (let i = 0; i < count; i++) {\n const key = inputKeys[i] as number;\n const digit = (key >>> shift) & 0xf;\n const target = offsets[digit] as number;\n offsets[digit] = target + 1;\n outputKeys[target] = key;\n outputValues[target] = inputValues[i] as number;\n }\n [inputKeys, outputKeys] = [outputKeys, inputKeys];\n [inputValues, outputValues] = [outputValues, inputValues];\n }\n return inputValues;\n}\n","/**\n * @role Bridge\n * Portable stable WebGPU radix sorting adapted to Three.js TSL compute nodes.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n Loop,\n atomicAdd,\n atomicLoad,\n atomicOr,\n atomicStore,\n countOneBits,\n float,\n floatBitsToUint,\n instanceIndex,\n int,\n invocationLocalIndex,\n ivec2,\n storage,\n storageBarrier,\n textureLoad,\n uint,\n uniform,\n workgroupArray,\n workgroupBarrier,\n workgroupId,\n} from 'three/tsl';\nimport type { SplatSorter } from './sorter';\nimport type { SplatSortMetric } from './splat-mesh-types';\nimport { releaseRendererAttributes } from './compute-sorter';\nimport {\n RADIX_BITS_PER_PASS,\n RADIX_EXACT_KEY_BITS,\n RADIX_KEY_BITS,\n RADIX_KEY_MAX,\n radixPassCount,\n} from './radix-sort';\nimport { viewDepthRadius, viewRadialRadius } from './splat-sort-bounds';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\nconst WORKGROUP_SIZE = 256;\nconst ELEMENTS_PER_THREAD = 8;\nconst ELEMENTS_PER_WORKGROUP = WORKGROUP_SIZE * ELEMENTS_PER_THREAD;\nconst DIGIT_COUNT = 16;\nconst MASK_WORDS_PER_DIGIT = WORKGROUP_SIZE / 32;\nconst MASK_WORDS_PER_GROUP = DIGIT_COUNT * MASK_WORDS_PER_DIGIT;\nconst SCAN_BLOCK_SIZE = 256;\n\ntype UintWorkgroupArray = {\n element(index: THREE.Node<'uint'>): THREE.Node<'uint'>;\n};\n\n/** Isolates gaps in Three's current WorkgroupInfo/Bitcount TypeScript declarations. */\nfunction asUintNode(node: unknown): THREE.Node<'uint'> {\n return node as THREE.Node<'uint'>;\n}\n\n/**\n * Stable four-bit-per-pass WebGPU radix sorter.\n *\n * The ranked scatter follows PlayCanvas/WebGPU-Radix-Sort's per-digit bitmask\n * design. Three.js does not currently expose atomic workgroup arrays in TSL,\n * so masks use disjoint global-storage slices per workgroup. Workgroup and\n * storage barriers retain the same stable ranking semantics without relying\n * on cross-workgroup execution order.\n */\nexport class RadixSorter implements SplatSorter {\n readonly kind = 'radix' as const;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly stages: THREE.ComputeNode[] = [];\n private readonly workingAttributes: THREE.StorageBufferAttribute[];\n /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */\n private readonly mirrors: StorageMirrorReleaser;\n /** Set by {@link dispose}; makes a second dispose a no-op. */\n private disposed = false;\n private readonly viewRow0 = uniform(new THREE.Vector4());\n private readonly viewRow1 = uniform(new THREE.Vector4());\n private readonly viewRow2 = uniform(new THREE.Vector4());\n private readonly depthMin = uniform(0);\n private readonly depthScale = uniform(0);\n private readonly activeCount = uniform(0);\n private readonly viewCenter = new THREE.Vector3();\n /** Exact mode avoids scene-bounds quantization entirely. */\n private readonly exactDepth: boolean;\n private readonly sortMetric: SplatSortMetric;\n\n constructor(options: {\n renderer: THREE.WebGPURenderer;\n capacity: number;\n centersTexture?: THREE.DataTexture;\n dataTextureWidth?: number;\n /** Gathered world-space centers for a unified renderer. */\n centersBuffer?: THREE.StorageBufferAttribute;\n splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n sourceIndexAttribute: THREE.StorageBufferAttribute;\n /** Keep every Float32 depth bit instead of quantizing to 24 bits. */\n exactDepth?: boolean;\n /** Camera-space ordering key. Default `'depth'`. */\n sortMetric?: SplatSortMetric;\n }) {\n const { capacity, centersTexture, dataTextureWidth, centersBuffer } = options;\n if (!centersTexture && !centersBuffer) {\n throw new Error('RadixSorter: provide centersTexture or centersBuffer.');\n }\n if (centersTexture && dataTextureWidth === undefined) {\n throw new Error('RadixSorter: dataTextureWidth is required with centersTexture.');\n }\n this.renderer = options.renderer;\n this.sortMetric = options.sortMetric ?? 'depth';\n const exactDepth = options.exactDepth === true;\n this.exactDepth = exactDepth;\n const passCount = radixPassCount(exactDepth ? RADIX_EXACT_KEY_BITS : RADIX_KEY_BITS);\n const groupCount = Math.ceil(capacity / ELEMENTS_PER_WORKGROUP);\n const histogramLength = DIGIT_COUNT * groupCount;\n const scanBlockCount = Math.ceil(histogramLength / SCAN_BLOCK_SIZE);\n\n const keysAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const keysBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const histogramAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(histogramLength),\n 1,\n );\n const scanSumsAttribute = new THREE.StorageBufferAttribute(new Uint32Array(scanBlockCount), 1);\n const masksAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(groupCount * MASK_WORDS_PER_GROUP),\n 1,\n );\n this.workingAttributes = [\n keysAAttribute,\n keysBAttribute,\n valuesAAttribute,\n valuesBAttribute,\n histogramAttribute,\n scanSumsAttribute,\n masksAttribute,\n ];\n this.mirrors = new StorageMirrorReleaser(this.workingAttributes);\n\n const keysA = storage(keysAAttribute, 'uint', capacity);\n const keysB = storage(keysBAttribute, 'uint', capacity);\n const valuesA = storage(valuesAAttribute, 'uint', capacity);\n const valuesB = storage(valuesBAttribute, 'uint', capacity);\n const source = storage(options.sourceIndexAttribute, 'uint', capacity);\n const order = storage(options.splatIndexAttribute, 'float', capacity);\n const workCenters = centersBuffer ? storage(centersBuffer, 'vec4', capacity) : null;\n const histogram = storage(histogramAttribute, 'uint', histogramLength).toAtomic();\n const scanSums = storage(scanSumsAttribute, 'uint', scanBlockCount);\n const masks = storage(masksAttribute, 'uint', groupCount * MASK_WORDS_PER_GROUP).toAtomic();\n\n const buildKeys = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = source.element(instanceIndex);\n const texel = centersTexture\n ? ivec2(\n int(poolIndex).mod(int(dataTextureWidth as number)),\n int(poolIndex).div(int(dataTextureWidth as number)),\n )\n : null;\n const center = workCenters\n ? workCenters.element(poolIndex).xyz\n : textureLoad(centersTexture, texel as THREE.Node<'ivec2'>).xyz;\n const viewZ = this.viewRow2.xyz.dot(center).add(this.viewRow2.w);\n const depth =\n this.sortMetric === 'radial'\n ? this.viewRow0.xyz\n .dot(center)\n .add(this.viewRow0.w)\n .pow2()\n .add(this.viewRow1.xyz.dot(center).add(this.viewRow1.w).pow2())\n .add(viewZ.pow2())\n .sqrt()\n .negate()\n : viewZ;\n if (exactDepth) {\n // IEEE-754 bits are monotonic only for positive values. Flip the\n // sign partition so ascending unsigned keys remain ascending numeric\n // depth, including VLAM's negative view-space Z convention.\n // Three's BitcastNode typings omit shift/xor; runtime nodes support them.\n const bits = asUintNode(floatBitsToUint(depth));\n const mask = bits\n .shiftRight(uint(31))\n .equal(uint(0))\n .select(uint(0x80000000), uint(0xffffffff));\n keysA.element(instanceIndex).assign(bits.bitXor(mask));\n } else {\n keysA\n .element(instanceIndex)\n .assign(depth.sub(this.depthMin).mul(this.depthScale).clamp(0, RADIX_KEY_MAX).toUint());\n }\n valuesA.element(instanceIndex).assign(poolIndex);\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(buildKeys);\n\n for (let pass = 0; pass < passCount; pass++) {\n const inputKeys = pass % 2 === 0 ? keysA : keysB;\n const outputKeys = pass % 2 === 0 ? keysB : keysA;\n const inputValues = pass % 2 === 0 ? valuesA : valuesB;\n const outputValues = pass % 2 === 0 ? valuesB : valuesA;\n const bit = uint(pass * RADIX_BITS_PER_PASS);\n\n const clearHistogram = Fn(() => {\n atomicStore(histogram.element(instanceIndex), uint(0));\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const buildHistogram = Fn(() => {\n const base = workgroupId.x.mul(uint(ELEMENTS_PER_WORKGROUP));\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = base.add(uint(i).mul(uint(WORKGROUP_SIZE))).add(invocationLocalIndex);\n If(float(index).lessThan(this.activeCount), () => {\n const digit = inputKeys.element(index).shiftRight(bit).bitAnd(uint(0xf));\n atomicAdd(histogram.element(digit.mul(uint(groupCount)).add(workgroupId.x)), uint(1));\n });\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n const scanBlocks = Fn(() => {\n const start = instanceIndex.mul(uint(SCAN_BLOCK_SIZE));\n const running = uint(0).toVar();\n Loop(SCAN_BLOCK_SIZE, ({ i }) => {\n const index = start.add(uint(i));\n If(index.lessThan(uint(histogramLength)), () => {\n const value = atomicLoad(histogram.element(index)).toVar();\n atomicStore(histogram.element(index), running);\n running.addAssign(value);\n });\n });\n scanSums.element(instanceIndex).assign(running);\n })().compute(scanBlockCount, [64]);\n\n const scanBlockSums = Fn(() => {\n If(instanceIndex.equal(uint(0)), () => {\n const running = uint(0).toVar();\n Loop(scanBlockCount, ({ i }) => {\n const value = scanSums.element(i).toVar();\n scanSums.element(i).assign(running);\n running.addAssign(value);\n });\n });\n })().compute(1, [64]);\n\n const addBlockOffsets = Fn(() => {\n const block = instanceIndex.div(uint(SCAN_BLOCK_SIZE));\n atomicStore(\n histogram.element(instanceIndex),\n atomicLoad(histogram.element(instanceIndex)).add(scanSums.element(block)),\n );\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const digitOffsets = workgroupArray('uint', DIGIT_COUNT) as unknown as UintWorkgroupArray;\n const rankedScatter = Fn(() => {\n const thread = invocationLocalIndex;\n const group = workgroupId.x;\n const maskBase = group.mul(uint(MASK_WORDS_PER_GROUP));\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n digitOffsets.element(thread).assign(uint(0));\n });\n If(thread.lessThan(uint(MASK_WORDS_PER_GROUP)), () => {\n atomicStore(masks.element(maskBase.add(thread)), uint(0));\n });\n storageBarrier();\n workgroupBarrier();\n\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = group\n .mul(uint(ELEMENTS_PER_WORKGROUP))\n .add(i.toUint().mul(uint(WORKGROUP_SIZE)))\n .add(thread);\n const valid = float(index).lessThan(this.activeCount);\n const key = uint(0).toVar();\n const value = uint(0).toVar();\n const digit = uint(0).toVar();\n If(valid, () => {\n key.assign(inputKeys.element(index));\n value.assign(inputValues.element(index));\n digit.assign(key.shiftRight(bit).bitAnd(uint(0xf)));\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const maskIndex = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT))).add(word);\n atomicOr(masks.element(maskIndex), uint(1).shiftLeft(bitInWord));\n });\n storageBarrier();\n workgroupBarrier();\n\n If(valid, () => {\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const digitBase = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT)));\n const localRank = digitOffsets.element(digit).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n If(uint(wordIndex).lessThan(word), () => {\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(wordIndex.toUint())))),\n ),\n );\n });\n });\n const precedingMask = uint(1).shiftLeft(bitInWord).sub(uint(1));\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(word))).bitAnd(precedingMask)),\n ),\n );\n const prefix = atomicLoad(histogram.element(digit.mul(uint(groupCount)).add(group)));\n const target = prefix.add(localRank);\n outputKeys.element(target).assign(key);\n outputValues.element(target).assign(value);\n });\n\n storageBarrier();\n workgroupBarrier();\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n const total = uint(0).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n const maskIndex = maskBase\n .add(thread.mul(uint(MASK_WORDS_PER_DIGIT)))\n .add(wordIndex.toUint());\n total.addAssign(asUintNode(countOneBits(atomicLoad(masks.element(maskIndex)))));\n atomicStore(masks.element(maskIndex), uint(0));\n });\n digitOffsets.element(thread).addAssign(total);\n });\n storageBarrier();\n workgroupBarrier();\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n this.stages.push(\n clearHistogram,\n buildHistogram,\n scanBlocks,\n scanBlockSums,\n addBlockOffsets,\n rankedScatter,\n );\n }\n\n const writeOrder = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n order.element(instanceIndex).assign(float(valuesA.element(instanceIndex)));\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(writeOrder);\n }\n\n sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean {\n if (activeCount === 0) return true;\n const m = modelView.elements;\n this.viewRow0.value.set(m[0], m[4], m[8], m[12]);\n this.viewRow1.value.set(m[1], m[5], m[9], m[13]);\n this.viewRow2.value.set(m[2], m[6], m[10], m[14]);\n this.activeCount.value = activeCount;\n if (!this.exactDepth) {\n this.viewCenter.copy(bounds.center).applyMatrix4(modelView);\n const viewRadius =\n this.sortMetric === 'radial'\n ? viewRadialRadius(modelView, bounds.radius)\n : viewDepthRadius(modelView, bounds.radius);\n const minimum =\n this.sortMetric === 'radial'\n ? -(this.viewCenter.length() + viewRadius)\n : this.viewCenter.z - viewRadius;\n const maximum =\n this.sortMetric === 'radial'\n ? -Math.max(0, this.viewCenter.length() - viewRadius)\n : this.viewCenter.z + viewRadius;\n this.depthMin.value = minimum;\n this.depthScale.value = RADIX_KEY_MAX / (maximum - minimum || 1);\n }\n this.stages[0]!.count = activeCount;\n this.stages[this.stages.length - 1]!.count = activeCount;\n this.renderer.compute(this.stages);\n // Key/value ping-pong, histogram, scan sums and masks are all written and\n // read entirely on the GPU - at 4 B per splat of capacity each, their JS\n // mirrors are ~16 B/splat of pure waste. Deliberately *not*\n // `sourceIndex`/`splatIndex`: those belong to the mesh and are rewritten\n // from the CPU every frame. Same ownership line as `releaseRendererAttributes`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n return true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const stage of this.stages) stage.dispose();\n releaseRendererAttributes(this.renderer, this.workingAttributes);\n }\n}\n"],"names":["RADIX_KEY_BITS","RADIX_EXACT_KEY_BITS","RADIX_BITS_PER_PASS","RADIX_KEY_MAX","radixPassCount","keyBits","WORKGROUP_SIZE","ELEMENTS_PER_THREAD","ELEMENTS_PER_WORKGROUP","DIGIT_COUNT","MASK_WORDS_PER_DIGIT","MASK_WORDS_PER_GROUP","SCAN_BLOCK_SIZE","RadixSorter","options","__publicField","uniform","THREE","capacity","centersTexture","dataTextureWidth","centersBuffer","exactDepth","passCount","groupCount","histogramLength","scanBlockCount","keysAAttribute","keysBAttribute","valuesAAttribute","valuesBAttribute","histogramAttribute","scanSumsAttribute","masksAttribute","StorageMirrorReleaser","keysA","storage","keysB","valuesA","valuesB","source","order","workCenters","histogram","scanSums","masks","buildKeys","Fn","If","float","instanceIndex","poolIndex","texel","ivec2","int","center","textureLoad","viewZ","depth","bits","floatBitsToUint","mask","uint","pass","inputKeys","outputKeys","inputValues","outputValues","bit","clearHistogram","atomicStore","buildHistogram","base","workgroupId","Loop","i","index","invocationLocalIndex","digit","atomicAdd","scanBlocks","start","running","value","atomicLoad","scanBlockSums","addBlockOffsets","block","digitOffsets","workgroupArray","rankedScatter","thread","group","maskBase","storageBarrier","workgroupBarrier","valid","key","word","bitInWord","maskIndex","atomicOr","digitBase","localRank","wordIndex","countOneBits","precedingMask","target","total","writeOrder","modelView","activeCount","bounds","m","viewRadius","viewRadialRadius","viewDepthRadius","minimum","maximum","stage","releaseRendererAttributes"],"mappings":"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"}