@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.
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/THIRD_PARTY_NOTICES.md +86 -0
- package/dist/budget-governor.d.ts +154 -0
- package/dist/camera-budget-governor.d.ts +234 -0
- package/dist/chunk-cache-budget.d.ts +156 -0
- package/dist/chunk-fetch-scheduler.d.ts +168 -0
- package/dist/chunk-loader.d.ts +85 -0
- package/dist/compute-sorter.d.ts +131 -0
- package/dist/create-splat-renderer.d.ts +99 -0
- package/dist/dataset-source.d.ts +55 -0
- package/dist/depth-of-field.d.ts +73 -0
- package/dist/effects.d.ts +337 -0
- package/dist/effects.js +180 -0
- package/dist/effects.js.map +1 -0
- package/dist/formats/ksplat/index.d.ts +8 -0
- package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
- package/dist/formats/ksplat.js +167 -0
- package/dist/formats/ksplat.js.map +1 -0
- package/dist/formats/lcc/collision-mesh.d.ts +37 -0
- package/dist/formats/lcc/collision-partition.d.ts +35 -0
- package/dist/formats/lcc/index.d.ts +16 -0
- package/dist/formats/lcc/lcc.d.ts +28 -0
- package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
- package/dist/formats/lcc/lcc2.d.ts +4 -0
- package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
- package/dist/formats/lcc/parse-lcc.d.ts +177 -0
- package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
- package/dist/formats/lcc.js +969 -0
- package/dist/formats/lcc.js.map +1 -0
- package/dist/formats/ply/index.d.ts +12 -0
- package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
- package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
- package/dist/formats/ply.js +230 -0
- package/dist/formats/ply.js.map +1 -0
- package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
- package/dist/formats/rad/index.d.ts +10 -0
- package/dist/formats/rad/parse-rad.d.ts +88 -0
- package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
- package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
- package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
- package/dist/formats/rad/rad.d.ts +132 -0
- package/dist/formats/rad.js +1098 -0
- package/dist/formats/rad.js.map +1 -0
- package/dist/formats/sog/index.d.ts +9 -0
- package/dist/formats/sog/parse-sog.d.ts +45 -0
- package/dist/formats/sog.js +257 -0
- package/dist/formats/sog.js.map +1 -0
- package/dist/formats/splat/index.d.ts +8 -0
- package/dist/formats/splat/parse-splat.d.ts +10 -0
- package/dist/formats/splat.js +39 -0
- package/dist/formats/splat.js.map +1 -0
- package/dist/formats/spz/index.d.ts +8 -0
- package/dist/formats/spz/parse-spz.d.ts +10 -0
- package/dist/formats/spz.js +218 -0
- package/dist/formats/spz.js.map +1 -0
- package/dist/frontier-worker-CAnK_Cmu.js +566 -0
- package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
- package/dist/half-float-Cs4FDq8Q.js +30 -0
- package/dist/half-float-Cs4FDq8Q.js.map +1 -0
- package/dist/half-float.d.ts +15 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +9054 -0
- package/dist/index.js.map +1 -0
- package/dist/load-scene.d.ts +50 -0
- package/dist/load-worker-protocol.d.ts +82 -0
- package/dist/loading-wLo8vRbA.js +102 -0
- package/dist/loading-wLo8vRbA.js.map +1 -0
- package/dist/loading.d.ts +150 -0
- package/dist/lod-manifest.d.ts +61 -0
- package/dist/lod-scheduler.d.ts +229 -0
- package/dist/lod-source.d.ts +207 -0
- package/dist/logging-BfPdd7NJ.js +19 -0
- package/dist/logging-BfPdd7NJ.js.map +1 -0
- package/dist/logging.d.ts +37 -0
- package/dist/one-shot-worker-G80btnth.js +664 -0
- package/dist/one-shot-worker-G80btnth.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/ply-header-BwgExCn-.js +125 -0
- package/dist/ply-header-BwgExCn-.js.map +1 -0
- package/dist/ply-header.d.ts +87 -0
- package/dist/radix-sort.d.ts +28 -0
- package/dist/radix-sorter.d.ts +46 -0
- package/dist/relighting.d.ts +50 -0
- package/dist/selection-volume.d.ts +87 -0
- package/dist/sh-pack-D5wAe5gg.js +54 -0
- package/dist/sh-pack-D5wAe5gg.js.map +1 -0
- package/dist/sh-pack.d.ts +49 -0
- package/dist/sort-scheduler.d.ts +62 -0
- package/dist/sort-worker-protocol.d.ts +37 -0
- package/dist/sorter.d.ts +31 -0
- package/dist/source-transform.d.ts +57 -0
- package/dist/splat-budget-PSojLJPO.js +157 -0
- package/dist/splat-budget-PSojLJPO.js.map +1 -0
- package/dist/splat-budget.d.ts +404 -0
- package/dist/splat-data-BZM5dhhx.js +10 -0
- package/dist/splat-data-BZM5dhhx.js.map +1 -0
- package/dist/splat-data.d.ts +150 -0
- package/dist/splat-depth-pack.d.ts +39 -0
- package/dist/splat-mesh-material.d.ts +255 -0
- package/dist/splat-mesh-picking.d.ts +87 -0
- package/dist/splat-mesh-pool.d.ts +241 -0
- package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
- package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +390 -0
- package/dist/splat-mesh.d.ts +737 -0
- package/dist/splat-modifier-stack.d.ts +38 -0
- package/dist/splat-modifier.d.ts +156 -0
- package/dist/splat-partition.d.ts +37 -0
- package/dist/splat-query.d.ts +51 -0
- package/dist/splat-scene.d.ts +127 -0
- package/dist/splat-sort-bounds.d.ts +11 -0
- package/dist/static-lod-splat-mesh.d.ts +53 -0
- package/dist/static-lod-worker-D2fyqQZO.js +474 -0
- package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
- package/dist/static-lod-worker-protocol.d.ts +37 -0
- package/dist/static-lod.d.ts +24 -0
- package/dist/storage-attribute-mirror.d.ts +103 -0
- package/dist/streamed-splat-mesh-utils.d.ts +146 -0
- package/dist/streamed-splat-mesh.d.ts +985 -0
- package/dist/unified-splat-renderer.d.ts +205 -0
- package/dist/webgpu-limits.d.ts +133 -0
- package/dist/work-buffer-gather.d.ts +119 -0
- package/dist/work-buffer-material.d.ts +42 -0
- package/dist/worker-sorter.d.ts +57 -0
- package/dist/xr-view.d.ts +92 -0
- package/package.json +135 -21
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-mesh decoded-chunk cache arbitration.
|
|
3
|
+
*
|
|
4
|
+
* Every {@link StreamedSplatMesh} caps its own decoded-chunk cache, and in
|
|
5
|
+
* `foveationMode: 'pagetable'` that cap is `min(2 GiB, this capture's decoded
|
|
6
|
+
* size)` - a number sized for *one* streamed scene. A scene of streamed markers
|
|
7
|
+
* therefore has no ceiling at all: thirteen markers plus a main are thirteen
|
|
8
|
+
* plus one independent caps, and because each is sized to its own capture, a
|
|
9
|
+
* desktop that fits them never evicts. The background sweep then runs to
|
|
10
|
+
* completion against every one of them, pulling every capture in the scene into
|
|
11
|
+
* RAM and keeping it there.
|
|
12
|
+
*
|
|
13
|
+
* This is the missing ceiling, at whole-mesh granularity: one budget shared by
|
|
14
|
+
* every streamed mesh in a scene (the same sharing model as `SplatPool` and
|
|
15
|
+
* {@link ChunkFetchScheduler}), splitting a scene total by the same
|
|
16
|
+
* camera-projected weight a `CameraBudgetGovernor` already computes for drawing.
|
|
17
|
+
*
|
|
18
|
+
* It bounds **retention**, not prefetching. The sweep still runs and still warms
|
|
19
|
+
* the cache; it simply stops at an allowance the whole scene agreed on rather
|
|
20
|
+
* than at the size of each capture. A mesh the camera approaches gets a larger
|
|
21
|
+
* allowance and resumes sweeping; one it leaves gives bytes back.
|
|
22
|
+
*
|
|
23
|
+
* The budget brokers **bytes**, never chunks: it does not know about
|
|
24
|
+
* `ChunkLoader`, chunk ids or workers, which is what keeps the eviction policy
|
|
25
|
+
* where it belongs - inside the frontier worker, which is the only place that
|
|
26
|
+
* knows what the current cut still needs.
|
|
27
|
+
*/
|
|
28
|
+
/** A streamed mesh, as the cache budget sees it. */
|
|
29
|
+
export interface ChunkCacheClient {
|
|
30
|
+
/**
|
|
31
|
+
* This mesh's camera-projected weight - larger means more of the cache.
|
|
32
|
+
*
|
|
33
|
+
* Read on demand rather than pushed, so the budget never holds a stale
|
|
34
|
+
* weight; a `CameraBudgetGovernor.weightOf` call is the intended source, and
|
|
35
|
+
* is the same one {@link ChunkFetchScheduler} reads. Zero (hidden or
|
|
36
|
+
* suspended) still keeps `perMeshFloorBytes`: a mesh whose coarse base has
|
|
37
|
+
* been evicted draws nothing at all when the camera comes back to it.
|
|
38
|
+
*/
|
|
39
|
+
weight(): number;
|
|
40
|
+
/**
|
|
41
|
+
* The most this mesh could ever put to use - for a page-table mesh,
|
|
42
|
+
* `min(PAGETABLE_CACHE_FLOOR_BYTES, estimateSceneDecodedBytes(scene))`.
|
|
43
|
+
*
|
|
44
|
+
* Bytes above it are handed to siblings that can use them, the way
|
|
45
|
+
* `BudgetGovernor` waterfills past a member's `maxBudget`. Without it a scene
|
|
46
|
+
* of one large capture and a dozen small markers would reserve most of the
|
|
47
|
+
* envelope for markers that cannot fill it.
|
|
48
|
+
*/
|
|
49
|
+
readonly ceilingBytes: number;
|
|
50
|
+
/**
|
|
51
|
+
* This mesh's allowance moved. The mesh forwards it to its frontier worker;
|
|
52
|
+
* nothing is dropped synchronously, because only the worker's own eviction
|
|
53
|
+
* pass knows which chunks the current cut still needs.
|
|
54
|
+
*/
|
|
55
|
+
onAllowanceChanged(bytes: number): void;
|
|
56
|
+
}
|
|
57
|
+
/** Options for {@link ChunkCacheBudget}. */
|
|
58
|
+
export interface ChunkCacheBudgetOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Decoded-chunk bytes every registered mesh may hold **in total**.
|
|
61
|
+
*
|
|
62
|
+
* This is the number that decides whether the budget helps at all. Too low
|
|
63
|
+
* and the focused mesh re-fetches chunks it just evicted; too high and it is
|
|
64
|
+
* inert, because each mesh's own `ceilingBytes` becomes binding again. Size
|
|
65
|
+
* it against the tab's heap, not against any one capture.
|
|
66
|
+
*/
|
|
67
|
+
totalBytes: number;
|
|
68
|
+
/**
|
|
69
|
+
* Bytes each mesh keeps regardless of weight. Default 32 MiB, matching
|
|
70
|
+
* `resolveCpuCacheBytes`'s own minimum.
|
|
71
|
+
*
|
|
72
|
+
* Without a floor a far mesh evicts its own coarse base and re-fetches it on
|
|
73
|
+
* the next reschedule, forever - the same thrash {@link
|
|
74
|
+
* ChunkFetchScheduler.perMeshFloor} exists to prevent on the network side.
|
|
75
|
+
*/
|
|
76
|
+
perMeshFloorBytes?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Minimum ms between weight-driven reallocations. Default 250, matching the
|
|
79
|
+
* idle reschedule interval - there is no point re-splitting faster than the
|
|
80
|
+
* meshes can act on it. Membership changes bypass it.
|
|
81
|
+
*/
|
|
82
|
+
minIntervalMs?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Relative allowance change below which a mesh is not notified. Default 0.15.
|
|
85
|
+
*
|
|
86
|
+
* Every notification is a worker post and, indirectly, an eviction pass. A
|
|
87
|
+
* camera drifting slowly would otherwise repost a 1% different number every
|
|
88
|
+
* quarter second for no change in behaviour.
|
|
89
|
+
*/
|
|
90
|
+
deadband?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A mesh's registration. Opaque to callers: hold it from {@link
|
|
94
|
+
* ChunkCacheBudget.register} and pass it back to read the allowance or
|
|
95
|
+
* unregister.
|
|
96
|
+
*/
|
|
97
|
+
export interface ChunkCacheHandle {
|
|
98
|
+
readonly client: ChunkCacheClient;
|
|
99
|
+
}
|
|
100
|
+
/** See the module comment. Construct one per scene and pass it to every mesh. */
|
|
101
|
+
export declare class ChunkCacheBudget {
|
|
102
|
+
private readonly entries;
|
|
103
|
+
private readonly perMeshFloorBytes;
|
|
104
|
+
private readonly minIntervalMs;
|
|
105
|
+
private readonly deadband;
|
|
106
|
+
private totalBytesValue;
|
|
107
|
+
private lastAllocationMs;
|
|
108
|
+
private disposed;
|
|
109
|
+
constructor(options: ChunkCacheBudgetOptions);
|
|
110
|
+
/** Bytes shared across every registered mesh. */
|
|
111
|
+
get totalBytes(): number;
|
|
112
|
+
/**
|
|
113
|
+
* Resizes the scene envelope and re-splits immediately. For a host reacting
|
|
114
|
+
* to a quality change or a device-memory signal.
|
|
115
|
+
*/
|
|
116
|
+
setTotalBytes(bytes: number): void;
|
|
117
|
+
/** Meshes currently registered. */
|
|
118
|
+
get clientCount(): number;
|
|
119
|
+
/**
|
|
120
|
+
* Adds a mesh and re-splits at once, so the caller can read its opening
|
|
121
|
+
* allowance out of {@link allowanceFor} before posting worker init.
|
|
122
|
+
*/
|
|
123
|
+
register(client: ChunkCacheClient): ChunkCacheHandle;
|
|
124
|
+
/** Drops a mesh and hands its bytes back to the siblings. */
|
|
125
|
+
unregister(handle: ChunkCacheHandle): void;
|
|
126
|
+
/** This mesh's current allowance in bytes, or 0 once unregistered. */
|
|
127
|
+
allowanceFor(handle: ChunkCacheHandle): number;
|
|
128
|
+
/**
|
|
129
|
+
* Re-splits after the camera moved. Call it once per frame, right beside
|
|
130
|
+
* {@link ChunkFetchScheduler.weightsChanged} - the two read the same weights,
|
|
131
|
+
* so cache and network follow the same measure.
|
|
132
|
+
*
|
|
133
|
+
* Rate-limited and dead-banded; calling it every frame is the intended use.
|
|
134
|
+
*/
|
|
135
|
+
weightsChanged(now?: number): void;
|
|
136
|
+
/**
|
|
137
|
+
* Releases every registration. Meshes are not disposed - the budget is shared
|
|
138
|
+
* and does not own them, exactly as a shared `SplatPool` does not own its
|
|
139
|
+
* meshes.
|
|
140
|
+
*/
|
|
141
|
+
dispose(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Cap-aware waterfill: weight-proportional shares, floored so every mesh can
|
|
144
|
+
* hold its coarse base, clamped at each mesh's `ceilingBytes`, and the surplus
|
|
145
|
+
* from clamped meshes redistributed over the rest until it settles.
|
|
146
|
+
*
|
|
147
|
+
* The invariant callers depend on is `Σ allowance <= totalBytes`. It holds in
|
|
148
|
+
* every branch, including the degenerate one where the floors alone exceed
|
|
149
|
+
* the envelope - there the floors are scaled down proportionally rather than
|
|
150
|
+
* silently overcommitting, because a budget that can be exceeded by
|
|
151
|
+
* registering more meshes is the bug this class exists to fix.
|
|
152
|
+
*/
|
|
153
|
+
private allocate;
|
|
154
|
+
/** Suppresses reposts for changes too small to alter any mesh's behaviour. */
|
|
155
|
+
private movedEnough;
|
|
156
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-mesh chunk-fetch arbitration.
|
|
3
|
+
*
|
|
4
|
+
* Every {@link StreamedSplatMesh} owns its own `ChunkLoader` worker and issues
|
|
5
|
+
* chunk requests toward its own in-flight cap, so a scene of streamed meshes
|
|
6
|
+
* fetches with no shared ordering: a marker the camera is pointed at competes
|
|
7
|
+
* for bandwidth and connection slots against a dozen distant ones, each of
|
|
8
|
+
* which is equally entitled to its own cap. Spark does not have this problem
|
|
9
|
+
* structurally - one global traversal orders every fetch want
|
|
10
|
+
* biggest-on-screen-first through one pager, so its network order *is* its
|
|
11
|
+
* visual priority.
|
|
12
|
+
*
|
|
13
|
+
* This is that ordering at whole-mesh granularity: one scheduler shared by
|
|
14
|
+
* every streamed mesh in a scene (the same sharing model as `SplatPool`),
|
|
15
|
+
* handing out a bounded number of fetch slots in proportion to the same
|
|
16
|
+
* camera-projected weight a `CameraBudgetGovernor` already computes for
|
|
17
|
+
* drawing. Within a mesh the existing order still applies - a frontier plan's
|
|
18
|
+
* `touched` list is already sorted biggest-on-screen first - so the two
|
|
19
|
+
* granularities compose.
|
|
20
|
+
*
|
|
21
|
+
* The scheduler brokers **slots**, never fetches: it does not know about
|
|
22
|
+
* `ChunkLoader`, URLs or workers, which is what keeps a future scene-level
|
|
23
|
+
* shared loader an independent change.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Why a mesh wants a slot. The kind is the mesh's own statement of intent, and
|
|
27
|
+
* decides what a weightless (hidden, or suspended) mesh may still do:
|
|
28
|
+
*
|
|
29
|
+
* - `priority` - detail the frontier asked for and does not have. The chunks
|
|
30
|
+
* actually on screen.
|
|
31
|
+
* - `base` - the camera-directed coarse base, and the pinned coverage every
|
|
32
|
+
* deferred swap substitutes from. A far mesh must keep trickling these or it
|
|
33
|
+
* has nothing to draw at all.
|
|
34
|
+
* - `sweep` - the background file-order sweep that pulls a whole capture into
|
|
35
|
+
* the worker cache so later camera moves are served from RAM. Pure
|
|
36
|
+
* pre-warming: valuable when a mesh is on screen, and the first thing to give
|
|
37
|
+
* up when it is not.
|
|
38
|
+
*/
|
|
39
|
+
export type ChunkFetchKind = 'priority' | 'base' | 'sweep';
|
|
40
|
+
/** A streamed mesh, as the scheduler sees it. */
|
|
41
|
+
export interface ChunkFetchClient {
|
|
42
|
+
/**
|
|
43
|
+
* This mesh's camera-projected weight - larger means more of the pipe.
|
|
44
|
+
* Zero means hidden or suspended: such a mesh keeps its floor for `priority`
|
|
45
|
+
* and `base` work but is denied `sweep` entirely.
|
|
46
|
+
*
|
|
47
|
+
* Read on demand rather than pushed, so the scheduler never holds a stale
|
|
48
|
+
* weight; a `CameraBudgetGovernor.weightOf` call is the intended source.
|
|
49
|
+
*/
|
|
50
|
+
weight(): number;
|
|
51
|
+
/**
|
|
52
|
+
* A slot may now be free. The mesh should re-run its reschedule, which
|
|
53
|
+
* re-issues whatever it still wants. The scheduler deliberately keeps no
|
|
54
|
+
* queue of pending requests: the reschedule paths are already idempotent
|
|
55
|
+
* re-issuers, so a poke carries strictly more current information than a
|
|
56
|
+
* request recorded when the camera was somewhere else.
|
|
57
|
+
*/
|
|
58
|
+
onSlotAvailable(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Abort in-flight fetches of this kind - the mesh's weight dropped to zero
|
|
61
|
+
* while it held slots. Aborted fetches release their slots through the normal
|
|
62
|
+
* path, so this is how a focus change hands the pipe over immediately rather
|
|
63
|
+
* than after the far meshes' current requests drain.
|
|
64
|
+
*/
|
|
65
|
+
shedFetches(kind: ChunkFetchKind): void;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link ChunkFetchScheduler}. */
|
|
68
|
+
export interface ChunkFetchSchedulerOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Total chunk fetches in flight across every registered mesh. Default 16.
|
|
71
|
+
*
|
|
72
|
+
* This is the number that decides whether the scheduler helps at all. Too low
|
|
73
|
+
* and a single mesh scene streams slower than it does today; too high and
|
|
74
|
+
* there is nothing to arbitrate, because the browser's own queueing (six per
|
|
75
|
+
* origin on HTTP/1.1; a much larger multiplexed window on HTTP/2) becomes the
|
|
76
|
+
* real scheduler again - and it orders by request time, not by what is on
|
|
77
|
+
* screen. Tune it from a measured concurrency high-water mark, not by feel.
|
|
78
|
+
*/
|
|
79
|
+
maxGlobalInflight?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Slots every registered mesh may hold regardless of weight. Default 1.
|
|
82
|
+
*
|
|
83
|
+
* Without a floor a scene's far meshes stop fetching entirely while the
|
|
84
|
+
* focused one streams, and a marker with no coarse coverage draws nothing -
|
|
85
|
+
* so the floor is what turns "far meshes wait" into "far meshes trickle".
|
|
86
|
+
*
|
|
87
|
+
* One slot is always granted regardless of this setting: a mesh entitled to
|
|
88
|
+
* zero would never fetch and never settle. Set this above 1 to widen every
|
|
89
|
+
* mesh's guaranteed trickle.
|
|
90
|
+
*/
|
|
91
|
+
perMeshFloor?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A mesh's registration. Opaque to callers: hold it from {@link
|
|
95
|
+
* ChunkFetchScheduler.register} and pass it back to acquire, release and
|
|
96
|
+
* unregister.
|
|
97
|
+
*/
|
|
98
|
+
export interface ChunkFetchHandle {
|
|
99
|
+
readonly client: ChunkFetchClient;
|
|
100
|
+
}
|
|
101
|
+
/** See the module comment. Construct one per scene and pass it to every mesh. */
|
|
102
|
+
export declare class ChunkFetchScheduler {
|
|
103
|
+
private readonly entries;
|
|
104
|
+
private readonly maxGlobalInflight;
|
|
105
|
+
private readonly perMeshFloor;
|
|
106
|
+
private inflightCount;
|
|
107
|
+
private disposed;
|
|
108
|
+
constructor(options?: ChunkFetchSchedulerOptions);
|
|
109
|
+
/** Total slots granted and not yet released. */
|
|
110
|
+
get inflight(): number;
|
|
111
|
+
/** Meshes currently registered. */
|
|
112
|
+
get clientCount(): number;
|
|
113
|
+
register(client: ChunkFetchClient): ChunkFetchHandle;
|
|
114
|
+
/**
|
|
115
|
+
* Drops a mesh. Slots it still holds are released here: a disposing mesh
|
|
116
|
+
* aborts its fetches, and those aborts land after the handle is gone.
|
|
117
|
+
*/
|
|
118
|
+
unregister(handle: ChunkFetchHandle): void;
|
|
119
|
+
/**
|
|
120
|
+
* Grants a fetch slot, or denies and remembers the demand.
|
|
121
|
+
*
|
|
122
|
+
* A denial is not a failure and must not feed a mesh's retry backoff - the
|
|
123
|
+
* mesh simply did not fetch this tick, and will be woken (or reschedule on
|
|
124
|
+
* its own within the idle interval) to ask again.
|
|
125
|
+
*/
|
|
126
|
+
tryAcquire(handle: ChunkFetchHandle, kind: ChunkFetchKind): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Returns a slot. Must be called exactly once for every granted acquire -
|
|
129
|
+
* on success, on failure **and** on abort - or the pipe leaks capacity until
|
|
130
|
+
* the scene is torn down.
|
|
131
|
+
*/
|
|
132
|
+
release(handle: ChunkFetchHandle): void;
|
|
133
|
+
/**
|
|
134
|
+
* Re-examines weights after the camera moved. Call it once per frame, right
|
|
135
|
+
* after the budget governor's own update.
|
|
136
|
+
*
|
|
137
|
+
* Meshes that just lost all weight shed their pre-warming sweeps, so a focus
|
|
138
|
+
* change frees the pipe now rather than when a dozen far requests happen to
|
|
139
|
+
* finish.
|
|
140
|
+
*/
|
|
141
|
+
weightsChanged(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Releases every registration. Meshes are not disposed - the scheduler is
|
|
144
|
+
* shared and does not own them, exactly as a shared `SplatPool` does not own
|
|
145
|
+
* its meshes.
|
|
146
|
+
*/
|
|
147
|
+
dispose(): void;
|
|
148
|
+
/**
|
|
149
|
+
* The most slots this mesh may hold: its weight-proportional share of the
|
|
150
|
+
* *whole* pipe, floored so it can always trickle and capped so its siblings
|
|
151
|
+
* can always reach their own floors.
|
|
152
|
+
*
|
|
153
|
+
* This is a ceiling, not a reservation. The global cap does the real
|
|
154
|
+
* limiting, so a heavy mesh can use capacity its idle siblings are leaving on
|
|
155
|
+
* the table, while a far mesh's ceiling stays at its floor however early it
|
|
156
|
+
* asks - which is what keeps the first mesh to reschedule from taking the
|
|
157
|
+
* pipe and holding it. Dividing the *remainder* after every participant's
|
|
158
|
+
* floor instead would invert the whole point on a large scene: thirteen
|
|
159
|
+
* markers and a main at floor 1 consume a 16-slot pipe entirely, leaving the
|
|
160
|
+
* focused marker a smaller share than the far ones it is competing with.
|
|
161
|
+
*
|
|
162
|
+
* The denominator counts every mesh that plausibly wants the pipe - all
|
|
163
|
+
* registered meshes except those both weightless and idle, plus the caller.
|
|
164
|
+
*/
|
|
165
|
+
private shareFor;
|
|
166
|
+
/** Pokes the heaviest mesh that was denied since its last grant. */
|
|
167
|
+
private wake;
|
|
168
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { SplatData } from './splat-data';
|
|
2
|
+
import type { RadChunkRangeRequest } from './load-worker-protocol';
|
|
3
|
+
import type { LccChunkParams } from './formats/lcc/parse-lcc';
|
|
4
|
+
import { type SplatFileLoadOptions, type SplatFormat, type SplatInputOptions } from './loading';
|
|
5
|
+
/**
|
|
6
|
+
* Client for the persistent scene/chunk loading workers. Multiple requests
|
|
7
|
+
* may be in flight; each can be cancelled through an `AbortSignal`, which
|
|
8
|
+
* aborts the worker-side fetches (useful when the camera has moved on
|
|
9
|
+
* before a chunk arrived).
|
|
10
|
+
*
|
|
11
|
+
* There are two workers behind this one class. The streaming worker is
|
|
12
|
+
* constructed eagerly and handles everything the LOD paths need; the one-shot
|
|
13
|
+
* worker ({@link ONE_SHOT_FORMATS}) is constructed the first time a `.spz`,
|
|
14
|
+
* `.splat` or `.ksplat` is requested. Both decode off the main thread - the
|
|
15
|
+
* split exists only to keep the one-shot parsers out of the published entry.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const loader = new ChunkLoader();
|
|
19
|
+
* const chunk = await loader.load('/scene/0_0/', { kind: 'directory' });
|
|
20
|
+
*/
|
|
21
|
+
export declare class ChunkLoader {
|
|
22
|
+
private readonly streaming;
|
|
23
|
+
/** The in-flight construction; `null` until a one-shot format is requested. */
|
|
24
|
+
private oneShot;
|
|
25
|
+
/** The constructed one-shot client, so `dispose` does not have to await. */
|
|
26
|
+
private oneShotClient;
|
|
27
|
+
private disposed;
|
|
28
|
+
/**
|
|
29
|
+
* Fetches and decodes one scene file or chunk directory.
|
|
30
|
+
*
|
|
31
|
+
* @param url - Scene URL, absolute or relative to the page.
|
|
32
|
+
* @param options.kind - 'file' (default) selects a parser by extension;
|
|
33
|
+
* 'directory' decodes an unbundled SOG chunk directory.
|
|
34
|
+
* @param options.format - Explicit parser; `lcc-bin` additionally needs
|
|
35
|
+
* `options.lcc` to say which byte range of the file to fetch.
|
|
36
|
+
* @param options.signal - Abort signal for cancellation; the returned
|
|
37
|
+
* promise then rejects with a `DOMException` named `AbortError`.
|
|
38
|
+
* @param options.lcc - Byte-range plumbing for `lcc-bin`. This path exists
|
|
39
|
+
* for {@link StreamedSplatMesh} and is **not** part of the stable surface;
|
|
40
|
+
* the type is nameable as `LccChunkParams` on `@voluma/vlam/formats/lcc`.
|
|
41
|
+
* @param options.rad - Byte-range plumbing for `rad-chunk`, on the same
|
|
42
|
+
* footing; the type is `RadChunkRangeRequest` on `@voluma/vlam/formats/rad`.
|
|
43
|
+
* @throws Rejects only with {@link SplatLoadError} (resolve, fetch, decode or
|
|
44
|
+
* worker failures - `error.phase` says which; a worker that cannot start at
|
|
45
|
+
* all, e.g. under a CSP that blocks `blob:` workers, reports
|
|
46
|
+
* `phase: 'worker'`) or an `Error` named `AbortError` (cancellation or
|
|
47
|
+
* {@link dispose}).
|
|
48
|
+
*/
|
|
49
|
+
load(url: string, options?: SplatInputOptions & {
|
|
50
|
+
kind?: 'file' | 'directory';
|
|
51
|
+
format?: SplatFormat | 'lcc-bin' | 'rad-chunk';
|
|
52
|
+
lcc?: LccChunkParams;
|
|
53
|
+
rad?: RadChunkRangeRequest;
|
|
54
|
+
sog?: {
|
|
55
|
+
packShBands: 1 | 2 | 3;
|
|
56
|
+
};
|
|
57
|
+
files?: Readonly<Record<string, string>>;
|
|
58
|
+
}): Promise<SplatData>;
|
|
59
|
+
/**
|
|
60
|
+
* Decodes a local file (from a file input or a drop) in the worker - the
|
|
61
|
+
* bytes are read worker-side, so a multi-million-splat decode still never
|
|
62
|
+
* blocks the main thread.
|
|
63
|
+
*
|
|
64
|
+
* Only self-contained files are supported: `.ply`, `.spz`, `.splat`,
|
|
65
|
+
* `.ksplat` and bundled `.sog`. An unbundled SOG directory needs sibling
|
|
66
|
+
* fetches and so requires {@link load} with `kind: 'directory'`.
|
|
67
|
+
*
|
|
68
|
+
* @param file - The file; its name selects the parser unless `format` is given.
|
|
69
|
+
* @throws Rejects only with {@link SplatLoadError} (unknown extension, read
|
|
70
|
+
* or decode failures) or an `Error` named `AbortError` (cancellation or
|
|
71
|
+
* {@link dispose}).
|
|
72
|
+
*/
|
|
73
|
+
loadFile(file: File, options?: SplatFileLoadOptions): Promise<SplatData>;
|
|
74
|
+
/** Routes one load to the worker that owns its format. */
|
|
75
|
+
private request;
|
|
76
|
+
/**
|
|
77
|
+
* The worker for `format`, constructing the one-shot worker on first need.
|
|
78
|
+
*
|
|
79
|
+
* The in-flight promise is cached, not just the resolved client, so two
|
|
80
|
+
* concurrent `.spz` loads share one worker instead of racing to build two.
|
|
81
|
+
*/
|
|
82
|
+
private clientFor;
|
|
83
|
+
/** Terminates both workers and rejects all in-flight requests. */
|
|
84
|
+
dispose(): void;
|
|
85
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import type { SplatSorter } from './sorter';
|
|
3
|
+
import type { uniformArray } from 'three/tsl';
|
|
4
|
+
/**
|
|
5
|
+
* Optional per-source world transform for a unified {@link SplatScene} pool.
|
|
6
|
+
* When present, each splat's center is transformed to world space by its
|
|
7
|
+
* source's matrix (selected by a per-splat id read from `sourceIdTexture`)
|
|
8
|
+
* before its depth is computed, so clouds with different world transforms sort
|
|
9
|
+
* against each other correctly. Absent for an ordinary single-transform mesh.
|
|
10
|
+
*/
|
|
11
|
+
export interface PerSourceSortTransform {
|
|
12
|
+
/** RedFormat float texture holding each pool slot's source id (the channel). */
|
|
13
|
+
sourceIdTexture: THREE.DataTexture;
|
|
14
|
+
/** Shared array of source-matrix columns; see `SourceMatrixArray`. */
|
|
15
|
+
columns: ReturnType<typeof uniformArray>;
|
|
16
|
+
/** CPU mirror of the source-id channel, used by the WebGL2 worker sorter. */
|
|
17
|
+
sourceIds: Float32Array;
|
|
18
|
+
/** Column-major source matrices, used by the WebGL2 worker sorter. */
|
|
19
|
+
matrices: Float32Array;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* GPU depth sorter: a single-pass counting sort over 2²² (~4M) depth
|
|
23
|
+
* buckets, running entirely in TSL compute passes on the WebGPU backend.
|
|
24
|
+
*
|
|
25
|
+
* 1. clear - zero the histogram
|
|
26
|
+
* 2. histogram - depth per active splat → bucket id; count per bucket
|
|
27
|
+
* 3. scanBlocks - exclusive prefix sum within each 256-bucket block
|
|
28
|
+
* 4. scanBlockSums - parallel exclusive scans over groups of block totals
|
|
29
|
+
* 5. scanSuperSums - exclusive scan over the remaining super-block totals
|
|
30
|
+
* 6. addBlockOffsets - combine both block-sum scan levels
|
|
31
|
+
* 7. addOffsets - combine block and bucket offsets
|
|
32
|
+
* 8. scatter - order[offset[bucket]++] = pool index (atomic)
|
|
33
|
+
*
|
|
34
|
+
* Why ~4M buckets, not 64K: on a large scene, 16-bit buckets made a
|
|
35
|
+
* near-camera bucket span thousands of overlapping grass/leaf splats whose
|
|
36
|
+
* arbitrary intra-bucket order reshuffled as the camera moved - visible
|
|
37
|
+
* popping. Each depth bucket is now a sub-splat-width slice, so splats that
|
|
38
|
+
* share a bucket are effectively coplanar and their (still arbitrary) order
|
|
39
|
+
* cannot be seen. (A multi-pass radix would give exact order but needs a
|
|
40
|
+
* *stable* scatter, which the parallel `atomicAdd` scatter is not - hence a
|
|
41
|
+
* single wide pass instead.)
|
|
42
|
+
*
|
|
43
|
+
* Dynamic capacity: the sorter walks the mesh's `sourceIndex` buffer (pool
|
|
44
|
+
* indices of the active splats) and sorts only the first `activeCount`
|
|
45
|
+
* entries, so ranges can be appended and removed without rebuilding
|
|
46
|
+
* pipelines - the per-splat passes are dispatched with a dynamic invocation
|
|
47
|
+
* count and guarded by an `activeCount` uniform.
|
|
48
|
+
*
|
|
49
|
+
* Portable by construction: cross-workgroup communication only happens
|
|
50
|
+
* through the implicit synchronization WebGPU guarantees between dispatches
|
|
51
|
+
* - no spin-waits or scheduling assumptions.
|
|
52
|
+
*
|
|
53
|
+
* The depth range for bucket quantization comes from the mesh's bounding
|
|
54
|
+
* sphere transformed to view space, so no GPU min/max reduction is needed.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ComputeSorter implements SplatSorter {
|
|
57
|
+
readonly kind: "counting";
|
|
58
|
+
private static readonly BUCKET_COUNT;
|
|
59
|
+
private static readonly BLOCK_SIZE;
|
|
60
|
+
/**
|
|
61
|
+
* Smallest bucket count a sort will dispatch. Below this the fixed passes
|
|
62
|
+
* are already cheap, and the floor keeps every block-scan index exact.
|
|
63
|
+
*/
|
|
64
|
+
private static readonly MIN_BUCKET_COUNT;
|
|
65
|
+
private readonly renderer;
|
|
66
|
+
private readonly clearPass;
|
|
67
|
+
private readonly histogramPass;
|
|
68
|
+
private readonly scanBlocksPass;
|
|
69
|
+
private readonly scanBlockSumsPass;
|
|
70
|
+
private readonly addBlockSumOffsetsPass;
|
|
71
|
+
private readonly addOffsetsPass;
|
|
72
|
+
private readonly scatterPass;
|
|
73
|
+
/** All stages in dependency order, submitted as one WebGPU compute pass. */
|
|
74
|
+
private readonly sortPasses;
|
|
75
|
+
/** Sorter-owned working buffers, released on {@link dispose}. */
|
|
76
|
+
private readonly workingAttributes;
|
|
77
|
+
/** Frees the JS mirrors three keeps behind the GPU-only working buffers. */
|
|
78
|
+
private readonly mirrors;
|
|
79
|
+
/** Set by {@link dispose}; makes a second dispose a no-op. */
|
|
80
|
+
private disposed;
|
|
81
|
+
/** Row 2 of the model-view matrix; view-space z = row2 · (position, 1). */
|
|
82
|
+
private readonly viewRow2;
|
|
83
|
+
private readonly depthMin;
|
|
84
|
+
private readonly depthScale;
|
|
85
|
+
/** Active splat count; a float compares exactly for counts < 2²⁴. */
|
|
86
|
+
private readonly activeCount;
|
|
87
|
+
/** Highest bucket index this sort uses; see {@link effectiveBucketCount}. */
|
|
88
|
+
private readonly bucketMax;
|
|
89
|
+
private readonly viewCenter;
|
|
90
|
+
constructor(options: {
|
|
91
|
+
renderer: THREE.WebGPURenderer;
|
|
92
|
+
/** Pool capacity in splats; all buffers are sized once from this. */
|
|
93
|
+
capacity: number;
|
|
94
|
+
/** RGBA32F texture holding splat centers (xyz), pool-indexed. */
|
|
95
|
+
centersTexture?: THREE.DataTexture;
|
|
96
|
+
/** Width of the centers texture, to map pool index → texel. */
|
|
97
|
+
dataTextureWidth?: number;
|
|
98
|
+
/** Work-buffer centers (xyzw per splat), as written by a gather pass. */
|
|
99
|
+
centersBuffer?: THREE.StorageBufferAttribute;
|
|
100
|
+
/** The float `splatIndex` buffer the render material reads. */
|
|
101
|
+
splatIndexAttribute: THREE.StorageInstancedBufferAttribute;
|
|
102
|
+
/** Pool indices of the active splats (first `activeCount` entries). */
|
|
103
|
+
sourceIndexAttribute: THREE.StorageBufferAttribute;
|
|
104
|
+
/** Per-source world transform for a unified pool; omit for a single mesh. */
|
|
105
|
+
perSource?: PerSourceSortTransform;
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Buckets to actually use for `activeCount` splats, rounded up to a power of
|
|
109
|
+
* two so block indexing stays exact.
|
|
110
|
+
*
|
|
111
|
+
* The histogram is allocated once for the pool's worst case, but clearing and
|
|
112
|
+
* scanning all 2²² buckets costs the same whether one splat is resident or
|
|
113
|
+
* four million - a fixed per-sort bill that dominates on a mobile GPU when
|
|
114
|
+
* the pool is only partly filled (a streaming scene ramping up, or any small
|
|
115
|
+
* scene). Sizing the dispatch to the live splat count keeps the design's
|
|
116
|
+
* ~1-bucket-per-splat depth resolution, so ordering is unaffected, while
|
|
117
|
+
* skipping work on buckets no splat can land in.
|
|
118
|
+
*/
|
|
119
|
+
private effectiveBucketCount;
|
|
120
|
+
sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean;
|
|
121
|
+
dispose(): void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Frees the GPU buffers of storage attributes that never sat in a geometry.
|
|
125
|
+
* The renderer has no public API for this, so it reaches for the renderer's
|
|
126
|
+
* attribute map directly; a harmless no-op if the internal shape changes, and
|
|
127
|
+
* idempotent (the map's `delete` ignores unknown entries). Shared by the
|
|
128
|
+
* sorters and by owners of mesh-level storage attributes (`sourceIndex`, the
|
|
129
|
+
* unified work buffer) on dispose.
|
|
130
|
+
*/
|
|
131
|
+
export declare function releaseRendererAttributes(renderer: THREE.WebGPURenderer, attributes: readonly THREE.BufferAttribute[]): void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call WebGPU renderer construction for VLAM! scenes.
|
|
3
|
+
*
|
|
4
|
+
* Every host needs the same four things right - adapter, device, raised limits
|
|
5
|
+
* and the platform-dependent `powerPreference` - and getting any of them wrong
|
|
6
|
+
* fails *late*: past ~8.4 M unified splats, or with MSAA silently dropped.
|
|
7
|
+
* {@link createSplatRenderer} is that block, once.
|
|
8
|
+
*
|
|
9
|
+
* The primitives it composes ({@link recommendedWebGpuRequiredLimits},
|
|
10
|
+
* {@link webGpuPowerPreferenceOptions}) stay exported for hosts that own device
|
|
11
|
+
* creation themselves - an XR session sharing a device, say.
|
|
12
|
+
*/
|
|
13
|
+
import { WebGPURenderer, type WebGPURendererParameters } from 'three/webgpu';
|
|
14
|
+
import { type WebGpuPowerPreference } from './webgpu-limits';
|
|
15
|
+
/** The device handle `createSplatRenderer` hands to `WebGPURenderer`. */
|
|
16
|
+
export interface SplatRendererGpuDevice {
|
|
17
|
+
readonly features: ReadonlySet<string>;
|
|
18
|
+
}
|
|
19
|
+
/** The adapter surface `createSplatRenderer` reads limits and features from. */
|
|
20
|
+
export interface SplatRendererGpuAdapter {
|
|
21
|
+
readonly features: ReadonlySet<string>;
|
|
22
|
+
readonly limits: {
|
|
23
|
+
readonly maxStorageBufferBindingSize: number;
|
|
24
|
+
readonly maxBufferSize: number;
|
|
25
|
+
readonly maxTextureDimension2D?: number;
|
|
26
|
+
};
|
|
27
|
+
requestDevice(descriptor?: {
|
|
28
|
+
requiredFeatures?: readonly string[];
|
|
29
|
+
requiredLimits?: Record<string, number>;
|
|
30
|
+
}): Promise<SplatRendererGpuDevice>;
|
|
31
|
+
}
|
|
32
|
+
/** The `navigator.gpu` surface `createSplatRenderer` probes. */
|
|
33
|
+
export interface SplatRendererGpu {
|
|
34
|
+
requestAdapter(options?: {
|
|
35
|
+
powerPreference?: WebGpuPowerPreference;
|
|
36
|
+
}): Promise<SplatRendererGpuAdapter | null>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for {@link createSplatRenderer}.
|
|
40
|
+
*
|
|
41
|
+
* Everything `THREE.WebGPURenderer` accepts passes straight through -
|
|
42
|
+
* `antialias`, `forceWebGL`, `trackTimestamp`, `canvas`, `alpha`, `samples`,
|
|
43
|
+
* `getFallback`, … - except the three members the helper owns (`device`,
|
|
44
|
+
* `requiredLimits`, and `powerPreference`, which is widened here to allow
|
|
45
|
+
* `null`).
|
|
46
|
+
*/
|
|
47
|
+
export interface CreateSplatRendererOptions extends Omit<WebGPURendererParameters, 'device' | 'requiredLimits' | 'powerPreference'> {
|
|
48
|
+
/**
|
|
49
|
+
* Adapter/renderer power hint. Omitted automatically on Windows, where Chrome
|
|
50
|
+
* ignores it and warns ([crbug.com/369219127](https://crbug.com/369219127)).
|
|
51
|
+
* Pass `null` to never send it.
|
|
52
|
+
*
|
|
53
|
+
* @defaultValue `'high-performance'`
|
|
54
|
+
*/
|
|
55
|
+
powerPreference?: WebGpuPowerPreference | null;
|
|
56
|
+
/**
|
|
57
|
+
* Throw instead of degrading to three's WebGL2 backend. Adapter and device
|
|
58
|
+
* failures rethrow their original error, unwrapped.
|
|
59
|
+
*
|
|
60
|
+
* @defaultValue `false`
|
|
61
|
+
*/
|
|
62
|
+
requireWebGpu?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The WebGPU entry point to probe. Defaults to `navigator.gpu`; pass `null`
|
|
65
|
+
* to skip the probe entirely (tests, non-DOM hosts).
|
|
66
|
+
*/
|
|
67
|
+
gpu?: SplatRendererGpu | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates a `THREE.WebGPURenderer` configured for splat rendering: the
|
|
71
|
+
* adapter's advertised buffer/texture maxima as `requiredLimits`, every adapter
|
|
72
|
+
* feature as `requiredFeatures`, and `powerPreference` only where the platform
|
|
73
|
+
* honours it.
|
|
74
|
+
*
|
|
75
|
+
* Both raised values matter late rather than loudly. Without the limits, large
|
|
76
|
+
* unified or streamed scenes throw past ~8.4 M splats. Without
|
|
77
|
+
* `core-features-and-limits` among the requested features, three treats the
|
|
78
|
+
* backend as compatibility-mode and **silently drops MSAA** - which is why the
|
|
79
|
+
* device is requested here rather than left to `WebGPUBackend.init`. Owning the
|
|
80
|
+
* request also keeps the real failure: three's `getFallback` hook logs a flat
|
|
81
|
+
* "WebGPU is not available" and drops the cause.
|
|
82
|
+
*
|
|
83
|
+
* Degrades to three's WebGL2 backend exactly as an unconfigured
|
|
84
|
+
* `new THREE.WebGPURenderer()` would, warning through
|
|
85
|
+
* {@link setVlamLogHandler} when the cause was a fixable failure rather than a
|
|
86
|
+
* browser without WebGPU. Pass `requireWebGpu: true` to throw instead.
|
|
87
|
+
*
|
|
88
|
+
* Does **not** call `setSize`, `renderer.init()`, or append the canvas - those
|
|
89
|
+
* stay in host code - and does not touch `outputColorSpace`, whose three.js
|
|
90
|
+
* default (sRGB) is already what the splat shader expects.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* const renderer = await createSplatRenderer();
|
|
94
|
+
* renderer.setSize(innerWidth, innerHeight);
|
|
95
|
+
* document.body.appendChild(renderer.domElement);
|
|
96
|
+
*
|
|
97
|
+
* @throws {TypeError} if `requireWebGpu` and `forceWebGL` are both set.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createSplatRenderer(options?: CreateSplatRendererOptions): Promise<WebGPURenderer>;
|