@voluma/vlam 0.1.0 → 0.2.1

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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/THIRD_PARTY_NOTICES.md +86 -0
  4. package/dist/budget-governor.d.ts +154 -0
  5. package/dist/camera-budget-governor.d.ts +234 -0
  6. package/dist/chunk-cache-budget.d.ts +156 -0
  7. package/dist/chunk-fetch-scheduler.d.ts +168 -0
  8. package/dist/chunk-loader.d.ts +85 -0
  9. package/dist/compute-sorter.d.ts +131 -0
  10. package/dist/create-splat-renderer.d.ts +99 -0
  11. package/dist/dataset-source.d.ts +55 -0
  12. package/dist/depth-of-field.d.ts +73 -0
  13. package/dist/effects.d.ts +337 -0
  14. package/dist/effects.js +180 -0
  15. package/dist/effects.js.map +1 -0
  16. package/dist/formats/ksplat/index.d.ts +8 -0
  17. package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
  18. package/dist/formats/ksplat.js +167 -0
  19. package/dist/formats/ksplat.js.map +1 -0
  20. package/dist/formats/lcc/collision-mesh.d.ts +37 -0
  21. package/dist/formats/lcc/collision-partition.d.ts +35 -0
  22. package/dist/formats/lcc/index.d.ts +16 -0
  23. package/dist/formats/lcc/lcc.d.ts +28 -0
  24. package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
  25. package/dist/formats/lcc/lcc2.d.ts +4 -0
  26. package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
  27. package/dist/formats/lcc/parse-lcc.d.ts +177 -0
  28. package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
  29. package/dist/formats/lcc.js +969 -0
  30. package/dist/formats/lcc.js.map +1 -0
  31. package/dist/formats/ply/index.d.ts +12 -0
  32. package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
  33. package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
  34. package/dist/formats/ply.js +230 -0
  35. package/dist/formats/ply.js.map +1 -0
  36. package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
  37. package/dist/formats/rad/index.d.ts +10 -0
  38. package/dist/formats/rad/parse-rad.d.ts +88 -0
  39. package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
  40. package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
  41. package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
  42. package/dist/formats/rad/rad.d.ts +132 -0
  43. package/dist/formats/rad.js +1098 -0
  44. package/dist/formats/rad.js.map +1 -0
  45. package/dist/formats/sog/index.d.ts +9 -0
  46. package/dist/formats/sog/parse-sog.d.ts +45 -0
  47. package/dist/formats/sog.js +257 -0
  48. package/dist/formats/sog.js.map +1 -0
  49. package/dist/formats/splat/index.d.ts +8 -0
  50. package/dist/formats/splat/parse-splat.d.ts +10 -0
  51. package/dist/formats/splat.js +39 -0
  52. package/dist/formats/splat.js.map +1 -0
  53. package/dist/formats/spz/index.d.ts +8 -0
  54. package/dist/formats/spz/parse-spz.d.ts +10 -0
  55. package/dist/formats/spz.js +218 -0
  56. package/dist/formats/spz.js.map +1 -0
  57. package/dist/frontier-worker-CAnK_Cmu.js +566 -0
  58. package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
  59. package/dist/half-float-Cs4FDq8Q.js +30 -0
  60. package/dist/half-float-Cs4FDq8Q.js.map +1 -0
  61. package/dist/half-float.d.ts +15 -0
  62. package/dist/index.d.ts +54 -0
  63. package/dist/index.js +9054 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/load-scene.d.ts +50 -0
  66. package/dist/load-worker-protocol.d.ts +82 -0
  67. package/dist/loading-wLo8vRbA.js +102 -0
  68. package/dist/loading-wLo8vRbA.js.map +1 -0
  69. package/dist/loading.d.ts +150 -0
  70. package/dist/lod-manifest.d.ts +61 -0
  71. package/dist/lod-scheduler.d.ts +229 -0
  72. package/dist/lod-source.d.ts +207 -0
  73. package/dist/logging-BfPdd7NJ.js +19 -0
  74. package/dist/logging-BfPdd7NJ.js.map +1 -0
  75. package/dist/logging.d.ts +37 -0
  76. package/dist/one-shot-worker-G80btnth.js +664 -0
  77. package/dist/one-shot-worker-G80btnth.js.map +1 -0
  78. package/dist/orientation.d.ts +35 -0
  79. package/dist/ply-header-BwgExCn-.js +125 -0
  80. package/dist/ply-header-BwgExCn-.js.map +1 -0
  81. package/dist/ply-header.d.ts +87 -0
  82. package/dist/radix-sort.d.ts +28 -0
  83. package/dist/radix-sorter.d.ts +46 -0
  84. package/dist/relighting.d.ts +50 -0
  85. package/dist/selection-volume.d.ts +87 -0
  86. package/dist/sh-pack-D5wAe5gg.js +54 -0
  87. package/dist/sh-pack-D5wAe5gg.js.map +1 -0
  88. package/dist/sh-pack.d.ts +49 -0
  89. package/dist/sort-scheduler.d.ts +62 -0
  90. package/dist/sort-worker-protocol.d.ts +37 -0
  91. package/dist/sorter.d.ts +31 -0
  92. package/dist/source-transform.d.ts +57 -0
  93. package/dist/splat-budget-PSojLJPO.js +157 -0
  94. package/dist/splat-budget-PSojLJPO.js.map +1 -0
  95. package/dist/splat-budget.d.ts +404 -0
  96. package/dist/splat-data-BZM5dhhx.js +10 -0
  97. package/dist/splat-data-BZM5dhhx.js.map +1 -0
  98. package/dist/splat-data.d.ts +150 -0
  99. package/dist/splat-depth-pack.d.ts +39 -0
  100. package/dist/splat-mesh-material.d.ts +255 -0
  101. package/dist/splat-mesh-picking.d.ts +87 -0
  102. package/dist/splat-mesh-pool.d.ts +241 -0
  103. package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
  104. package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
  105. package/dist/splat-mesh-types.d.ts +390 -0
  106. package/dist/splat-mesh.d.ts +737 -0
  107. package/dist/splat-modifier-stack.d.ts +38 -0
  108. package/dist/splat-modifier.d.ts +156 -0
  109. package/dist/splat-partition.d.ts +37 -0
  110. package/dist/splat-query.d.ts +51 -0
  111. package/dist/splat-scene.d.ts +127 -0
  112. package/dist/splat-sort-bounds.d.ts +11 -0
  113. package/dist/static-lod-splat-mesh.d.ts +53 -0
  114. package/dist/static-lod-worker-D2fyqQZO.js +474 -0
  115. package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
  116. package/dist/static-lod-worker-protocol.d.ts +37 -0
  117. package/dist/static-lod.d.ts +24 -0
  118. package/dist/storage-attribute-mirror.d.ts +103 -0
  119. package/dist/streamed-splat-mesh-utils.d.ts +146 -0
  120. package/dist/streamed-splat-mesh.d.ts +985 -0
  121. package/dist/unified-splat-renderer.d.ts +205 -0
  122. package/dist/webgpu-limits.d.ts +133 -0
  123. package/dist/work-buffer-gather.d.ts +119 -0
  124. package/dist/work-buffer-material.d.ts +42 -0
  125. package/dist/worker-sorter.d.ts +57 -0
  126. package/dist/xr-view.d.ts +92 -0
  127. package/package.json +135 -21
@@ -0,0 +1,205 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { type DepthOfFieldSettings } from './depth-of-field';
3
+ import { type RelightingSettings, type RelightingUniforms } from './relighting';
4
+ import { SplatMesh } from './splat-mesh';
5
+ import type { SplatPickOptions, SplatPickResult } from './splat-mesh';
6
+ /** Registration settings for one source in a {@link UnifiedSplatRenderer}. */
7
+ export interface UnifiedSplatSourceOptions {
8
+ /** Whole-source opacity applied after its modifier stack. Defaults to `1`. */
9
+ opacity?: number;
10
+ /** Whether this source contributes to the unified draw. Defaults to `true`. */
11
+ visible?: boolean;
12
+ /** Higher values survive a fixed work-buffer overflow first. Defaults to `0`. */
13
+ priority?: number;
14
+ /**
15
+ * Cache a modifier-bearing source until {@link UnifiedSplatRenderer.invalidateSource}
16
+ * is called. Defaults to `false`, which safely re-gathers live modifier
17
+ * uniforms every frame.
18
+ *
19
+ * Use this for static modifiers, or when the host has a natural dirty signal
20
+ * for every uniform/channel mutation. Forgetting to invalidate leaves the
21
+ * unified work-buffer output stale.
22
+ */
23
+ cacheModifiers?: boolean;
24
+ }
25
+ /**
26
+ * Result of a successful {@link UnifiedSplatRenderer.pick}: the frontmost hit
27
+ * across every visible registered source, tagged with the source it landed on.
28
+ */
29
+ export interface UnifiedSplatPickResult extends SplatPickResult {
30
+ /** The registered source mesh whose splat produced the winning hit. */
31
+ readonly source: SplatMesh;
32
+ }
33
+ /**
34
+ * Construction settings shared by every source in a unified renderer.
35
+ *
36
+ * @experimental May change in a minor release.
37
+ */
38
+ export interface UnifiedSplatRendererOptions {
39
+ /** Composite source colors in display (sRGB) space. Defaults to `false`. */
40
+ srgbOutput?: boolean;
41
+ /**
42
+ * Global depth-sort strategy. `'exact'` preserves every Float32 depth bit
43
+ * through a stable radix sort of gathered world-space centers. `'counting'`
44
+ * remains the lower-cost default for existing callers.
45
+ *
46
+ * @experimental Exact sorting trades two additional radix passes for better
47
+ * ordering in large scenes with dense foliage or overlapping surfaces.
48
+ */
49
+ sortStrategy?: 'counting' | 'exact';
50
+ }
51
+ /**
52
+ * Returns true when `renderer` can drive {@link UnifiedSplatRenderer}.
53
+ * Heterogeneous gather/sort/draw is WebGPU-only; WebGL2 hosts keep standalone
54
+ * `SplatMesh` draws or static {@link SplatScene}.
55
+ */
56
+ export declare function supportsUnifiedSplatRenderer(renderer: object): boolean;
57
+ /**
58
+ * One gather → global-sort → draw path for static and streamed splat sources.
59
+ * Sources retain their own residency and update lifecycle; this mesh gathers
60
+ * their current active pool ranges into one shared work buffer per frame.
61
+ *
62
+ * The unified mesh stays at identity (`matrixAutoUpdate` is off). Pose each
63
+ * registered source mesh instead - sorter bounds and the draw material both
64
+ * consume world-space centers written by gather.
65
+ *
66
+ * WebGPU only. Prefer {@link supportsUnifiedSplatRenderer} before construction.
67
+ *
68
+ * @experimental May change in a minor release.
69
+ */
70
+ export declare class UnifiedSplatRenderer extends THREE.Mesh {
71
+ private readonly workBuffer;
72
+ private readonly workSourceIndex;
73
+ /** The sorted draw-order buffer; lives outside the geometry, freed on dispose. */
74
+ private readonly orderAttribute;
75
+ /**
76
+ * Frees the JS mirrors three keeps behind `workSourceIndex` and `order`.
77
+ *
78
+ * `workSourceIndex` is an identity ramp written once at construction; `order`
79
+ * is written only by the sorter, on the GPU. Neither is ever read back, so
80
+ * both mirrors are 4 B/slot of dead JS heap once uploaded.
81
+ */
82
+ private readonly mirrors;
83
+ private readonly sorter;
84
+ private readonly renderer;
85
+ private readonly sources;
86
+ private previousLayout;
87
+ /** Previous layout keyed by source; kept in step with {@link previousLayout}. */
88
+ private readonly previousLayoutBySource;
89
+ /** Per-prepare scratch lists, reused so a steady frame allocates nothing. */
90
+ private readonly candidateScratch;
91
+ private readonly admittedScratch;
92
+ private readonly drawingBufferSize;
93
+ /** Reused target dimensions for secondary views; avoids per-view garbage. */
94
+ private readonly secondaryViewSize;
95
+ /**
96
+ * Camera gating for the global sorter, mirroring the standalone
97
+ * `SplatMesh.requestSortIfNeeded` path: an unchanged work buffer under a
98
+ * stationary camera skips the whole-buffer re-sort. Content changes
99
+ * (regather, layout change) force the next sort through the scheduler.
100
+ */
101
+ private readonly sortScheduler;
102
+ /** Model-view of the last accepted sort; starts unmatchable (zero scale). */
103
+ private readonly lastSortedModelView;
104
+ /**
105
+ * Total admitted splats in the last prepared frame. `removeSource` filters
106
+ * {@link previousLayout} directly, so a tail source's removal can leave the
107
+ * remaining entries identical - only this total betrays the shrink, and a
108
+ * shrunk draw range must re-sort (the old permutation indexes past it).
109
+ */
110
+ private previousAdmittedTotal;
111
+ private readonly bounds;
112
+ private readonly focal;
113
+ private readonly viewport;
114
+ private readonly maxStdDev;
115
+ private readonly minSplatSizePx;
116
+ private readonly antialias;
117
+ private readonly projectedLowPassVariance;
118
+ private readonly compensateProjectedLowPass;
119
+ private readonly dofFocusDistance;
120
+ private readonly dofAperture;
121
+ private readonly relightPlaceholder;
122
+ private relightMap;
123
+ private readonly relightBlend;
124
+ private readonly relightBrightness;
125
+ private readonly relightBackground;
126
+ private readonly relightSoftness;
127
+ private readonly srgbOutput;
128
+ private sourceMaxStdDev;
129
+ private sourceAntialias;
130
+ private sourceProjectedFilterProfile;
131
+ private overflowedSourceCount;
132
+ private overflowedSplatCount;
133
+ private disposed;
134
+ constructor(renderer: THREE.WebGPURenderer, capacity: number, options?: UnifiedSplatRendererOptions);
135
+ /** Fixed work-buffer splat capacity chosen at construction. */
136
+ get capacity(): number;
137
+ /** Registers a source. The caller keeps ownership and may still query it. */
138
+ addSource(source: SplatMesh, options?: UnifiedSplatSourceOptions): void;
139
+ /**
140
+ * Invalidates one registered source's cached gather output.
141
+ *
142
+ * Required after changing modifier uniforms or channels when that source was
143
+ * registered with `cacheModifiers: true`. The following {@link update}
144
+ * re-gathers the source and re-sorts because modifiers may move splats.
145
+ * Returns `false` when the source is not registered.
146
+ */
147
+ invalidateSource(source: SplatMesh): boolean;
148
+ /** Number of whole sources omitted in the most recent update due to capacity. */
149
+ get droppedSourceCount(): number;
150
+ /** Number of active splats omitted in the most recent update due to capacity. */
151
+ get droppedSplatCount(): number;
152
+ /** Updates a source's resolved opacity without rebuilding its gather pipeline. */
153
+ setSourceOpacity(source: SplatMesh, opacity: number): void;
154
+ /** Shows or hides a source without changing its residency. */
155
+ setSourceVisible(source: SplatMesh, visible: boolean): void;
156
+ /**
157
+ * Core projected-2D depth of field on the unified draw pass (not gather).
158
+ * Live uniforms - no gather-cache invalidation. Pass `aperture: 0` to disable.
159
+ */
160
+ setDepthOfField(settings: Partial<DepthOfFieldSettings>): void;
161
+ /** Current core DoF uniforms (`aperture === 0` means off). */
162
+ getDepthOfField(): DepthOfFieldSettings;
163
+ /**
164
+ * PlayCanvas-style proxy-mesh relighting on the unified draw pass (not gather).
165
+ * Live blend/brightness/background; map identity change rebuilds the draw
166
+ * material. Does not invalidate gather caches. Pass `null` to disable.
167
+ */
168
+ setRelighting(options: RelightingSettings | null): void;
169
+ /** Current relight numeric uniforms (`blend === 0` means off). */
170
+ getRelighting(): RelightingUniforms;
171
+ /**
172
+ * Asynchronously picks the frontmost visible splat under an NDC coordinate
173
+ * across every registered source.
174
+ *
175
+ * Semantics: each source that is
176
+ * currently visible in this unified renderer runs its own depth-tested GPU
177
+ * pick pass; the hit nearest the camera wins and the result names its source
178
+ * mesh. Hidden sources (`setSourceVisible(source, false)`) never hit. A hit
179
+ * whose source was removed, hidden, or disposed while its readback was in
180
+ * flight is dropped rather than misattributed. Resolves `null` for misses,
181
+ * an empty source list, and picks pending across {@link dispose}.
182
+ *
183
+ * Uses the construction renderer. Concurrent calls are safe: each source
184
+ * serializes its own picks and shared renderer state is restored
185
+ * synchronously before any await.
186
+ */
187
+ pick(ndc: THREE.Vector2, camera: THREE.Camera, options?: SplatPickOptions): Promise<UnifiedSplatPickResult | null>;
188
+ /** Unregisters a source and restores its standalone draw visibility. */
189
+ removeSource(source: SplatMesh): boolean;
190
+ /** Updates source LODs, gathers their active splats, then globally sorts them. */
191
+ update(camera: THREE.PerspectiveCamera): void;
192
+ /**
193
+ * Re-sorts and draws this unified source list for a secondary camera.
194
+ * Mirrors and portals use this instead of rendering the hidden source meshes
195
+ * individually, so their reflection keeps one global transparent order.
196
+ */
197
+ renderView(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, target?: THREE.RenderTarget | null): void;
198
+ private prepare;
199
+ dispose(): void;
200
+ private assertNotDisposed;
201
+ /** Rebuilds the EWA draw material when the relight map identity changes. */
202
+ private rebuildDrawMaterial;
203
+ private createGather;
204
+ private buildGather;
205
+ }
@@ -0,0 +1,133 @@
1
+ import type { WebGPURenderer } from 'three/webgpu';
2
+ /**
3
+ * WebGPU device limits hosts should request for VLAM beyond the spec defaults.
4
+ *
5
+ * The WebGPU default `maxStorageBufferBindingSize` is 128 MiB. Unified work
6
+ * buffers and large streamed pools allocate storage attributes past that
7
+ * (16 bytes per splat for an RGBA32F centers buffer). Desktop adapters often
8
+ * advertise ~2 GiB, but only when those values are passed as `requiredLimits`
9
+ * to `adapter.requestDevice()` / Three.js `WebGPURenderer`.
10
+ *
11
+ * `maxTextureDimension2D` is here for the same reason: the default is 8192,
12
+ * which caps a pool at 8192 rows of {@link SPLAT_DATA_TEXTURE_WIDTH} (≈16.7 M
13
+ * splats of *capacity*, so ≈11.2 M resident at the usual 1.5 slack). Desktop
14
+ * adapters commonly advertise 16384 and double that ceiling for free.
15
+ */
16
+ export interface WebGpuRequiredLimits {
17
+ maxStorageBufferBindingSize: number;
18
+ maxBufferSize: number;
19
+ maxTextureDimension2D: number;
20
+ [limit: string]: number;
21
+ }
22
+ /** Bytes per splat in the largest unified work-buffer attribute (RGBA32F centers). */
23
+ export declare const WORK_BUFFER_CENTERS_BYTES_PER_SPLAT = 16;
24
+ /**
25
+ * Bytes per work-buffer slot across *all* of its attributes: centers, colors,
26
+ * covarianceA and covarianceB at RGBA32F (16 each), plus the isotropic mix and
27
+ * screen-radius scalars (4 each).
28
+ */
29
+ export declare const WORK_BUFFER_BYTES_PER_SLOT: number;
30
+ /**
31
+ * WebGPU's default `maxStorageBufferBindingSize` when the host does not raise
32
+ * it via `requiredLimits` (128 MiB).
33
+ */
34
+ export declare const WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 134217728;
35
+ /**
36
+ * Copies the adapter's advertised buffer maxima for use as
37
+ * `WebGPURenderer({ requiredLimits })`.
38
+ *
39
+ * Does not call `requestDevice` - the host owns device creation.
40
+ */
41
+ export declare function recommendedWebGpuRequiredLimits(adapter: {
42
+ limits: {
43
+ maxStorageBufferBindingSize: number;
44
+ maxBufferSize: number;
45
+ maxTextureDimension2D?: number;
46
+ };
47
+ }): WebGpuRequiredLimits;
48
+ /** Mirrors WebGPU `GPUPowerPreference` without requiring `@webgpu/types`. */
49
+ export type WebGpuPowerPreference = 'high-performance' | 'low-power';
50
+ type NavigatorPlatformProbe = Pick<Navigator, 'platform' | 'userAgent'> & {
51
+ userAgentData?: {
52
+ platform?: string;
53
+ };
54
+ };
55
+ /**
56
+ * Whether the host should pass `powerPreference` to `navigator.gpu.requestAdapter`
57
+ * / Three.js `WebGPURenderer`.
58
+ *
59
+ * Chrome on Windows ignores the option and logs a console warning when it is
60
+ * present ([crbug.com/369219127](https://crbug.com/369219127)). Prefer
61
+ * {@link webGpuPowerPreferenceOptions} so Windows hosts omit it automatically.
62
+ */
63
+ export declare function supportsWebGpuPowerPreference(nav?: NavigatorPlatformProbe | undefined): boolean;
64
+ /**
65
+ * Options to spread into `requestAdapter(...)` and `WebGPURenderer({...})`.
66
+ * Empty on Windows so Chrome does not warn about an ignored `powerPreference`.
67
+ */
68
+ export declare function webGpuPowerPreferenceOptions(powerPreference?: WebGpuPowerPreference, nav?: NavigatorPlatformProbe): {
69
+ powerPreference: WebGpuPowerPreference;
70
+ } | Record<string, never>;
71
+ /**
72
+ * Byte size of the largest single storage-buffer bind VLAM allocates for a
73
+ * given splat capacity (unified work-buffer centers: `capacity × 16`).
74
+ *
75
+ * Use with adapter/device limits to decide whether to raise `requiredLimits`
76
+ * or lower the budget before constructing {@link UnifiedSplatRenderer}.
77
+ */
78
+ export declare function estimateLargestStorageBufferBytes(capacity: number): number;
79
+ /**
80
+ * Estimates what a {@link UnifiedSplatRenderer}'s work buffer costs, in bytes,
81
+ * once the scene has rendered a frame.
82
+ *
83
+ * This used to be double the figure below, and the doubling was not a detail.
84
+ * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,
85
+ * and three's WebGPU backend never releases it: the
86
+ * `attribute.onUploadCallback()` call that would is missing from the backend's
87
+ * buffer upload path. So a work buffer of N slots cost
88
+ * N × {@link WORK_BUFFER_BYTES_PER_SLOT} on the GPU *and* the same again on the
89
+ * JS heap, permanently, even though nothing ever reads the CPU copy back - the
90
+ * gather pass writes these buffers entirely on the GPU.
91
+ *
92
+ * `WorkBuffer.releaseCpuMirrors` now drops those mirrors after the first
93
+ * dispatch that uploads them (see `storage-attribute-mirror`), so steady state
94
+ * is the GPU side alone. The *peak* is still double - the array has to exist
95
+ * for the upload copy - which is what
96
+ * {@link estimateUnifiedWorkBufferPeakBytes} reports.
97
+ *
98
+ * Either figure is invisible to `estimateSplatPoolBytes`, which prices the
99
+ * splat *pool* and knows nothing about the unified renderer's scratch space. A
100
+ * host sizing a scene for a memory-constrained device wants both.
101
+ *
102
+ * @param capacity - Work-buffer capacity in splats.
103
+ * @returns Estimated steady-state bytes.
104
+ * @throws {RangeError} if `capacity` is not a non-negative finite number.
105
+ */
106
+ export declare function estimateUnifiedWorkBufferBytes(capacity: number): number;
107
+ /**
108
+ * Peak bytes a {@link UnifiedSplatRenderer}'s work buffer occupies: the window
109
+ * between construction and the first dispatch, while the GPU buffer and the JS
110
+ * array three copied it from are both alive.
111
+ *
112
+ * Ask this one when the question is "will allocating this scene succeed" - an
113
+ * out-of-memory failure happens against the peak. Ask
114
+ * {@link estimateUnifiedWorkBufferBytes} when the question is "what will this
115
+ * scene hold once it is up".
116
+ *
117
+ * @param capacity - Work-buffer capacity in splats.
118
+ * @returns Estimated bytes, GPU plus the not-yet-released CPU mirror.
119
+ * @throws {RangeError} if `capacity` is not a non-negative finite number.
120
+ */
121
+ export declare function estimateUnifiedWorkBufferPeakBytes(capacity: number): number;
122
+ /**
123
+ * The renderer's WebGPU `maxStorageBufferBindingSize`, or 0 when it cannot be
124
+ * read (WebGL2 fallback, or device not yet available). Callers skip the check
125
+ * when 0 rather than falsely reject.
126
+ */
127
+ export declare function deviceMaxStorageBufferBindingSize(renderer: WebGPURenderer): number;
128
+ /**
129
+ * Throws when a planned storage buffer would exceed the device bind limit.
130
+ * No-ops when the limit cannot be read (WebGL2 / uninitialized backend).
131
+ */
132
+ export declare function assertStorageBufferFitsDevice(renderer: WebGPURenderer, byteSize: number, capacity: number): void;
133
+ export {};
@@ -0,0 +1,119 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { type SplatShInputs, type Vec3Uniform } from './splat-mesh-material';
3
+ import type { SplatModifier } from './splat-modifier';
4
+ /**
5
+ * GPU gather pass for Phase 0 of the unified renderer. It resolves a source
6
+ * pool's active local centers into a shared world-space storage buffer. The
7
+ * work-buffer sorter consumes this buffer directly, so source-local pools can
8
+ * later be heterogeneous (including streamed LOD pools).
9
+ *
10
+ * Internal while M15.4's draw path and public source API are completed.
11
+ */
12
+ /** Shared GPU storage for every gathered source in one unified frame. */
13
+ export declare class WorkBuffer {
14
+ readonly capacity: number;
15
+ /** World-space center xyz; w is the drawable flag (1 draw, 0 modifier-hidden). */
16
+ readonly centers: THREE.StorageBufferAttribute;
17
+ /** Resolved color and opacity per work slot. */
18
+ readonly colors: THREE.StorageBufferAttribute;
19
+ /** First packed covariance row per work slot. */
20
+ readonly covarianceA: THREE.StorageBufferAttribute;
21
+ /** Second packed covariance row per work slot. */
22
+ readonly covarianceB: THREE.StorageBufferAttribute;
23
+ /** Per-slot isotropic covariance mix (0 = keep Σ, 1 = round dot). */
24
+ readonly isotropicMix: THREE.StorageBufferAttribute;
25
+ /** Per-slot isotropic target sigma radius in screen px (0 = disabled). */
26
+ readonly isotropicScreenRadius: THREE.StorageBufferAttribute;
27
+ /**
28
+ * Slots past {@link capacity}, reserved so a pipeline warm-up has somewhere
29
+ * harmless to write. See {@link WorkBufferGather.warmUp}: compiling a compute
30
+ * pipeline requires actually dispatching it, and dispatching zero workgroups
31
+ * - while it does compile - makes WebGPU log an "unusual workgroup count of 0"
32
+ * warning for every source. One workgroup aimed at this tail compiles just the
33
+ * same, silently, and cannot disturb a drawn slot: admission never allocates
34
+ * past `capacity` and the draw's `instanceCount` never covers it.
35
+ */
36
+ static readonly SCRATCH_SLOTS = 256;
37
+ constructor(capacity: number);
38
+ /**
39
+ * Drops the JS-heap copies three keeps behind these storage buffers.
40
+ *
41
+ * Every one of them is written only by the gather compute pass and read only
42
+ * by the draw material - the CPU side is zeroes that no code ever reads, and
43
+ * three's WebGPU backend never frees it on its own. At 72 B/slot that mirror
44
+ * is ~720 MB of JS heap on a 10M-slot scene. See `storage-attribute-mirror`.
45
+ *
46
+ * Safe to call after any dispatch that bound this buffer; a no-op once
47
+ * settled, and on WebGL2 or a renderer double that has no WebGPU backend.
48
+ */
49
+ releaseCpuMirrors(renderer: THREE.WebGPURenderer): void;
50
+ private readonly mirrors;
51
+ }
52
+ export declare class WorkBufferGather {
53
+ /** World-space `xyzw` center buffer, one element per unified work slot. */
54
+ readonly centers: THREE.StorageBufferAttribute;
55
+ /** Resolved source RGBA values; production draw consumes this next. */
56
+ readonly colors: THREE.StorageBufferAttribute;
57
+ /** Packed symmetric world-space covariance rows. */
58
+ readonly covarianceA: THREE.StorageBufferAttribute;
59
+ readonly covarianceB: THREE.StorageBufferAttribute;
60
+ readonly isotropicMix: THREE.StorageBufferAttribute;
61
+ readonly isotropicScreenRadius: THREE.StorageBufferAttribute;
62
+ /** Shared target for this source's gathered range. */
63
+ readonly workBuffer: WorkBuffer;
64
+ private readonly capacity;
65
+ private readonly activeCount;
66
+ private readonly targetOffset;
67
+ private readonly sourceMatrix;
68
+ private readonly cameraViewMatrix;
69
+ private readonly opacity;
70
+ private readonly pass;
71
+ constructor(options: {
72
+ capacity: number;
73
+ sourceCapacity: number;
74
+ sourceIndex: THREE.StorageBufferAttribute;
75
+ centersTexture: THREE.DataTexture;
76
+ colorsTexture: THREE.DataTexture;
77
+ covarianceATexture: THREE.DataTexture;
78
+ covarianceBTexture: THREE.DataTexture;
79
+ dataTextureWidth: number;
80
+ sh?: SplatShInputs | null;
81
+ modifiers?: readonly SplatModifier[];
82
+ channels?: ReadonlyMap<string, {
83
+ texture: THREE.DataTexture;
84
+ }>;
85
+ localCameraPosition?: Vec3Uniform;
86
+ srgbOutput?: boolean;
87
+ /**
88
+ * Source stores Spark LOD alpha (`alpha ÷ 2`, `.rad`). Recover the full
89
+ * `alpha ∈ [0,2]` here so the shared draw material sees one convention;
90
+ * `alpha > 1` then marks a merged node (see `createWorkBufferMaterial`).
91
+ */
92
+ lodAlpha?: boolean;
93
+ /** Reuse one target across all source gather passes. */
94
+ workBuffer?: WorkBuffer;
95
+ });
96
+ /** Gathers a source's active pool slots into a contiguous work-buffer range. */
97
+ gather(renderer: THREE.WebGPURenderer, activeCount: number, targetOffset: number, sourceMatrix: THREE.Matrix4, cameraViewMatrixOrOpacity?: THREE.Matrix4 | number, opacity?: number): void;
98
+ /**
99
+ * Compiles this gather's compute pipeline without gathering anything.
100
+ *
101
+ * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when
102
+ * the node graph is built. Measured on a 13-marker scene, a source's *first*
103
+ * `gather` took up to 2.0s while every later dispatch of the same pipeline
104
+ * took under 0.4ms, and because `gather` runs inside `update` that compile
105
+ * landed whole in one render frame. Running it through the async path here
106
+ * compiles off the critical frame, so the first real gather finds it ready.
107
+ *
108
+ * The dispatch targets {@link WorkBuffer.SCRATCH_SLOTS} past the drawable
109
+ * capacity rather than dispatching zero workgroups: zero also compiles, but
110
+ * WebGPU warns about it once per source, and writing into a drawn slot could
111
+ * leave junk behind when a cached gather is reused instead of re-run.
112
+ *
113
+ * Best-effort: a failure here costs only the stall it was meant to avoid, so
114
+ * callers may ignore it.
115
+ */
116
+ warmUp(renderer: THREE.WebGPURenderer): Promise<void>;
117
+ /** Releases the gather pipeline. The owner releases the storage attribute. */
118
+ dispose(): void;
119
+ }
@@ -0,0 +1,42 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import type { FloatUniform, Vec2Uniform } from './splat-mesh-material';
3
+ /**
4
+ * Creates the unified EWA draw material for gathered world-space splats.
5
+ *
6
+ * Its vertex stage reads both the sorter-written order and the gather-written
7
+ * centers, covariance, and resolved color from storage buffers. It keeps the
8
+ * verified ±3σ / exp(-4.5·|q|²) convention used by {@link SplatMesh}.
9
+ */
10
+ export declare function createWorkBufferMaterial(options: {
11
+ capacity: number;
12
+ centers: THREE.StorageBufferAttribute;
13
+ colors: THREE.StorageBufferAttribute;
14
+ covarianceA: THREE.StorageBufferAttribute;
15
+ covarianceB: THREE.StorageBufferAttribute;
16
+ isotropicMix: THREE.StorageBufferAttribute;
17
+ isotropicScreenRadius: THREE.StorageBufferAttribute;
18
+ order: THREE.StorageInstancedBufferAttribute;
19
+ focal: Vec2Uniform;
20
+ viewport: Vec2Uniform;
21
+ maxStdDev: FloatUniform;
22
+ /** Screen-space minimum splat radius in px; `0` disables. */
23
+ minSplatSizePx: FloatUniform;
24
+ antialias: FloatUniform;
25
+ /** Format-selected projected low-pass variance, in px². */
26
+ projectedLowPassVariance: FloatUniform;
27
+ /** LCC always preserves the low-pass integral; standard follows antialias. */
28
+ compensateProjectedLowPass: FloatUniform;
29
+ /** Core projected-2D DoF focus plane. Live; `0` aperture disables. */
30
+ dofFocusDistance: FloatUniform;
31
+ dofAperture: FloatUniform;
32
+ /**
33
+ * Proxy-mesh relight map (RGB = lit, A = coverage). Display only.
34
+ * Host swaps the texture object and rebuilds when enabling/changing map.
35
+ */
36
+ relightMap: THREE.Texture;
37
+ relightBlend: FloatUniform;
38
+ relightBrightness: FloatUniform;
39
+ relightBackground: FloatUniform;
40
+ /** Coverage soft edge in screen pixels; `0` = hard mask. */
41
+ relightSoftness: FloatUniform;
42
+ }): THREE.NodeMaterial;
@@ -0,0 +1,57 @@
1
+ import type * as THREE from 'three/webgpu';
2
+ import type { SplatSorter } from './sorter';
3
+ /**
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.
8
+ *
9
+ * One sort runs at a time; requests that arrive while the worker is busy
10
+ * are declined so the caller retries with the then-current camera on a
11
+ * later frame.
12
+ */
13
+ export declare class WorkerSorter implements SplatSorter {
14
+ readonly kind: "worker";
15
+ private readonly worker;
16
+ private readonly splatIndexAttribute;
17
+ private readonly host;
18
+ private inFlight;
19
+ /** Set by {@link dispose}; drops any already-delivered order message. */
20
+ private disposed;
21
+ /** The active spans the in-flight sort was computed against. */
22
+ private sentSpans;
23
+ constructor(host: WorkerSorterHost);
24
+ sort(modelView: THREE.Matrix4, _activeCount: number, _bounds: THREE.Sphere): boolean;
25
+ dispose(): void;
26
+ /** Sends written pool rows to keep the worker's centers mirror current. */
27
+ private pushCenters;
28
+ private applyOrder;
29
+ }
30
+ /** What the sorter needs from its mesh; see SplatMesh.createSorter. */
31
+ export interface WorkerSorterHost {
32
+ /** Pool capacity in splats. */
33
+ readonly capacity: number;
34
+ /** Splats per pool texture row. */
35
+ readonly rowWidth: number;
36
+ /** The pool's centers backing array (vec4 stride; xyz used). */
37
+ readonly centers: Float32Array;
38
+ /** Source metadata for a unified pool; omitted for a normal mesh. */
39
+ readonly perSource?: {
40
+ readonly sourceIds: Float32Array;
41
+ readonly matrices: Float32Array;
42
+ };
43
+ readonly splatIndexAttribute: THREE.InstancedBufferAttribute;
44
+ /** Drains the row spans written since the last call. */
45
+ takeDirtyRows(): {
46
+ start: number;
47
+ count: number;
48
+ }[];
49
+ /** Active ranges as (start, count) pool-index pairs, active-list order. */
50
+ getActiveSpans(): Uint32Array;
51
+ /**
52
+ * Called after a worker order lands in the draw list, so the host knows the
53
+ * list holds a sorted permutation (not identity active order) and must fully
54
+ * resync - not patch - it on the next active-list mutation.
55
+ */
56
+ onOrderApplied?(): void;
57
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Resolving the active WebXR view for a presenting renderer.
3
+ *
4
+ * Every render path (a static {@link SplatMesh}, a streamed one, the unified
5
+ * renderer) needs the same three things while an immersive session presents -
6
+ * a cyclopean camera to sort and place SH by, one eye camera to take the
7
+ * projection from, and the *per-eye* viewport in pixels - so they all resolve
8
+ * them here rather than each reading `renderer.xr` its own way.
9
+ *
10
+ * Internal. Nothing here is exported from `index.ts`.
11
+ */
12
+ import * as THREE from 'three/webgpu';
13
+ /** The cameras and pixel size describing one frame of a presenting XR session. */
14
+ export interface XrView {
15
+ /**
16
+ * The XR array camera: the head pose, midway between the eyes. Depth order
17
+ * and the SH view direction come from here - at a ~63 mm IPD the per-eye
18
+ * difference is imperceptible except within the near plane, so one
19
+ * cyclopean sort serves both eyes instead of doubling the frame's dominant
20
+ * cost. Its `projectionMatrix` is the union frustum covering both eyes,
21
+ * which is also the right frustum for LOD scheduling.
22
+ */
23
+ head: THREE.Camera;
24
+ /**
25
+ * The first eye camera, for projection-derived quantities. Left and right
26
+ * XR projections differ only in their center offset, not their focal
27
+ * length, so a focal length taken from one eye is exact for both - it is
28
+ * not an approximation.
29
+ */
30
+ eye: THREE.PerspectiveCamera;
31
+ /** Per-eye viewport width in device pixels. */
32
+ width: number;
33
+ /** Per-eye viewport height in device pixels. */
34
+ height: number;
35
+ }
36
+ /**
37
+ * The active XR view, or `null` when the renderer is not presenting one.
38
+ *
39
+ * **Freshens the XR camera before reading it.** three splits its per-frame XR
40
+ * work in two: the sub-cameras' *local* `matrix`, `projectionMatrix` and
41
+ * `viewport` are set in the XR manager's own animation-frame callback (which
42
+ * runs before the host's loop body), but their `matrixWorld` /
43
+ * `matrixWorldInverse`, the head's world matrices, and the two-eye union
44
+ * projection are only computed in `XRManager.updateCamera()` - which the
45
+ * renderer calls from inside `render()`. A `SplatMesh.update()` that runs
46
+ * before `render()` would therefore read last frame's world pose, and on the
47
+ * first presenting frame an identity one (sorting the scene from the world
48
+ * origin). Calling `updateCamera` here closes that gap; it is idempotent and
49
+ * cheap (a handful of matrix products - `session.updateRenderState` only
50
+ * fires when near/far actually change), so the renderer's own call moments
51
+ * later costs nothing.
52
+ *
53
+ * @param camera - The *application* camera. `updateCamera` reads its near/far,
54
+ * layers and parent, so the app camera is required here - not the head.
55
+ * @returns The resolved view, or `null` when not presenting (or before the
56
+ * session has produced its first viewer pose).
57
+ */
58
+ export declare function resolveXrView(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer): XrView | null;
59
+ /**
60
+ * Whether `camera` is an XR array camera. Code that needs a single frustum -
61
+ * picking, anything unprojecting depth through `near`/`far` - must reject one
62
+ * rather than silently read the defaults off a camera that has neither.
63
+ */
64
+ export declare function isXrArrayCamera(camera: THREE.Camera): boolean;
65
+ /**
66
+ * Session options for `navigator.xr.requestSession('immersive-vr', …)` that
67
+ * match the renderer's backend.
68
+ *
69
+ * three's XR manager **throws** out of `setSession` when a WebGPU-backed
70
+ * renderer meets a session that did not enable the `webgpu` feature - it has a
71
+ * genuine WebGPU XR path (an `XRGPUBinding` projection layer) and refuses to
72
+ * silently fall back to WebGL. three's own `VRButton` never asks for that
73
+ * feature, so a host pairing it with a `WebGPURenderer` fails to enter VR on
74
+ * exactly the browsers that support XR best. This returns the init that avoids
75
+ * the mismatch:
76
+ *
77
+ * ```js
78
+ * const session = await navigator.xr.requestSession('immersive-vr', xrSessionInit(renderer));
79
+ * await renderer.xr.setSession(session);
80
+ * ```
81
+ *
82
+ * `webgpu` is marked **required** rather than optional deliberately: an
83
+ * optional feature the browser declines still yields a session, which is the
84
+ * very mismatch that throws. Required turns it into a `requestSession`
85
+ * rejection the host can catch and explain - typically by falling back to a
86
+ * WebGL2 renderer, since three's own `WebGLXRFallback` helper swaps the entire
87
+ * renderer and is unusable once GPU resources are bound to the current one.
88
+ *
89
+ * @param base - Extra options to merge. Its `requiredFeatures` and
90
+ * `optionalFeatures` are concatenated, not replaced.
91
+ */
92
+ export declare function xrSessionInit(renderer: THREE.WebGPURenderer, base?: XRSessionInit): XRSessionInit;