@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,985 @@
|
|
|
1
|
+
export * from './streamed-splat-mesh-utils';
|
|
2
|
+
import * as THREE from 'three/webgpu';
|
|
3
|
+
import { SplatMesh, type SplatChannelOptions, type SplatMeshOptions, type SplatUpdateOptions } from './splat-mesh';
|
|
4
|
+
import type { StaticLodSplatMesh, StaticLodSplatMeshLoadOptions } from './static-lod-splat-mesh';
|
|
5
|
+
import { type StreamedScene } from './lod-source';
|
|
6
|
+
import type { CollisionMeshTile } from './formats/lcc/collision-mesh';
|
|
7
|
+
import { type SplatRequestOptions, type StreamedSplatFormat } from './loading';
|
|
8
|
+
import { type SplatDeviceProfile } from './splat-budget';
|
|
9
|
+
import { type FrontierFoveation } from './formats/rad/frontier-worker-protocol';
|
|
10
|
+
import type { ChunkFetchScheduler } from './chunk-fetch-scheduler';
|
|
11
|
+
import type { ChunkCacheBudget } from './chunk-cache-budget';
|
|
12
|
+
/**
|
|
13
|
+
* Rough decoded size of a whole streamed scene, for sizing the cache floor:
|
|
14
|
+
* positions (12 B) + colors (4 B) + covariances (24 B) per splat, plus the LOD
|
|
15
|
+
* tree arrays a `.rad` chunk carries (`childCount` 4 B + `childStart` 4 B +
|
|
16
|
+
* `size` 4 B) and packed SH when the scene carries it. Deliberately an
|
|
17
|
+
* over-estimate - the floor should never be the reason a capture cannot hold
|
|
18
|
+
* itself, and it must match what the frontier worker charges its cache, or a
|
|
19
|
+
* capture the cap was sized to hold starts evicting itself mid-load.
|
|
20
|
+
*
|
|
21
|
+
* Exported for unit testing only; not part of the public API.
|
|
22
|
+
*/
|
|
23
|
+
export declare function estimateSceneDecodedBytes(scene: StreamedScene): number;
|
|
24
|
+
/**
|
|
25
|
+
* Read-only startup-hold progress for classic `.lcc` {@link StreamedSplatMeshOptions.initialReveal}.
|
|
26
|
+
* Exported for hosts that gate visibility on the first useful near-L0 frame.
|
|
27
|
+
*/
|
|
28
|
+
export type InitialRevealState = {
|
|
29
|
+
readonly status: 'disabled';
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: 'pending';
|
|
32
|
+
readonly stagedSplats: number;
|
|
33
|
+
readonly totalSplats: number;
|
|
34
|
+
readonly readyGroups: number;
|
|
35
|
+
readonly totalGroups: number;
|
|
36
|
+
} | {
|
|
37
|
+
readonly status: 'ready';
|
|
38
|
+
} | {
|
|
39
|
+
readonly status: 'degraded';
|
|
40
|
+
readonly reason: 'capacity' | 'fetch-failed' | 'timeout';
|
|
41
|
+
readonly stagedSplats: number;
|
|
42
|
+
readonly totalSplats: number;
|
|
43
|
+
readonly readyGroups: number;
|
|
44
|
+
readonly totalGroups: number;
|
|
45
|
+
};
|
|
46
|
+
/** Options for {@link StreamedSplatMesh.load}. */
|
|
47
|
+
export interface StreamedSplatMeshOptions extends SplatMeshOptions {
|
|
48
|
+
/** Active-splat budget. Defaults to {@link resolveSplatBudget}. */
|
|
49
|
+
budget?: number;
|
|
50
|
+
/**
|
|
51
|
+
* A ceiling on the *resolved default* budget, for callers that want to
|
|
52
|
+
* tighten without overriding what the library knows.
|
|
53
|
+
*
|
|
54
|
+
* `budget` is absolute: it wins over the device tier, the format's cost class
|
|
55
|
+
* and everything else, because a caller who names a number has said they know
|
|
56
|
+
* better. That is the right contract, and the wrong tool for "the same as
|
|
57
|
+
* usual, but no more than N" - which is what a performance toggle or a host
|
|
58
|
+
* default actually means. Pinning a number there has twice shipped as a bug:
|
|
59
|
+
* a demo performance mode that *raised* the load on the weakest device tested,
|
|
60
|
+
* and a host whose default bypassed every device tier.
|
|
61
|
+
*
|
|
62
|
+
* Applied only when `budget` is omitted, and only downward - it never raises
|
|
63
|
+
* a budget the device would not otherwise have taken. It also suppresses the
|
|
64
|
+
* finest-level lift, which exists to raise a budget far enough to hold a
|
|
65
|
+
* scene whole and is exactly what "no more than N" rules out. Unrelated to
|
|
66
|
+
* {@link maxBudget}, which sizes the pool and bounds
|
|
67
|
+
* {@link StreamedSplatMesh.setBudget}.
|
|
68
|
+
*
|
|
69
|
+
* Forwarded to `resolveSplatBudget` as `SplatBudgetOptions.cap`.
|
|
70
|
+
*
|
|
71
|
+
* @throws {RangeError} at load if not a positive finite number.
|
|
72
|
+
*/
|
|
73
|
+
budgetCap?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Device signals for budget / quality defaults. Defaults to
|
|
76
|
+
* {@link detectSplatDeviceProfile}. Pass a profile enriched with
|
|
77
|
+
* {@link probeSplatGpuClass} so desktop integrated GPUs take the laptop
|
|
78
|
+
* tier instead of the workstation 8M path.
|
|
79
|
+
*/
|
|
80
|
+
deviceProfile?: SplatDeviceProfile;
|
|
81
|
+
/**
|
|
82
|
+
* Ceiling {@link StreamedSplatMesh.setBudget} may raise this mesh to, and the
|
|
83
|
+
* size its pool is allocated from. Defaults to `budget`.
|
|
84
|
+
*
|
|
85
|
+
* Set this above `budget` when a `CameraBudgetGovernor` or `BudgetGovernor`
|
|
86
|
+
* should be able to *grow* this mesh's share: the pool is allocated once at
|
|
87
|
+
* construction and never grows, so without headroom reserved here a governed
|
|
88
|
+
* mesh can only ever be shrunk below the budget it was built with. That is
|
|
89
|
+
* the whole reason a hand-split `pool / N` marker stays coarse near the
|
|
90
|
+
* camera - every marker's ceiling was fixed at a quarter of the pool.
|
|
91
|
+
*
|
|
92
|
+
* It is not free: the pool costs its *ceiling* in memory whether or not the
|
|
93
|
+
* budget ever reaches it (~64 B of GPU pool plus ~56 B of CPU backing per
|
|
94
|
+
* splat, 1.5× for capacity slack). Price it with `estimateSplatPoolBytes`
|
|
95
|
+
* before choosing - for several markers the
|
|
96
|
+
* sum of the ceilings is what has to fit, not the shared budget. A ceiling
|
|
97
|
+
* around 1.5–2× a member's fair share is usually the right trade.
|
|
98
|
+
*
|
|
99
|
+
* @throws {RangeError} at load if below `budget`, or not a positive finite
|
|
100
|
+
* number.
|
|
101
|
+
*/
|
|
102
|
+
maxBudget?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Multiplier on this mesh's LOD detail, matching Spark's per-mesh `lodScale`:
|
|
105
|
+
* `> 1` refines further (finer cut, more splats drawn), `< 1` coarsens.
|
|
106
|
+
* Default `1`.
|
|
107
|
+
*
|
|
108
|
+
* **`.rad` `foveationMode: 'pagetable'` only** - it scales the frontier cut
|
|
109
|
+
* the page-table traversal is given (`pixel_scale × lodScale ≤ limit`, exactly
|
|
110
|
+
* Spark's formula). It does nothing on a mesh with no per-splat cut to scale:
|
|
111
|
+
* a moderate `.rad` read as a chunk prefix, or a Streamed SOG / LCC scene. For
|
|
112
|
+
* the GPU cut modes (`'band'` / `'frontier'`) the equivalent is
|
|
113
|
+
* {@link SplatMeshOptions.foveationTargetPx} at `1 / lodScale`.
|
|
114
|
+
*
|
|
115
|
+
* The draw budget still bounds the result, so raising this past the point
|
|
116
|
+
* where the budget binds sharpens nothing - give the mesh budget as well.
|
|
117
|
+
*/
|
|
118
|
+
lodScale?: number;
|
|
119
|
+
/** Explicit format; by default the manifest's extension decides. */
|
|
120
|
+
format?: StreamedSplatFormat;
|
|
121
|
+
/** Serializable fetch settings for the manifest and its chunks. */
|
|
122
|
+
request?: SplatRequestOptions;
|
|
123
|
+
/**
|
|
124
|
+
* Cancels the load: the manifest fetch aborts, and {@link StreamedSplatMesh.load}
|
|
125
|
+
* rejects with a `DOMException` named `AbortError`. A mesh partially built
|
|
126
|
+
* when the signal fires is disposed - nothing leaks. Only read during load;
|
|
127
|
+
* later streaming is stopped by {@link SplatMesh.dispose}.
|
|
128
|
+
*/
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
/** Base URL a relative manifest URL resolves against (like {@link loadScene}). */
|
|
131
|
+
baseUrl?: string | URL;
|
|
132
|
+
/** World-unit distance inside which the finest LOD is used. Default 10. */
|
|
133
|
+
lodBaseDistance?: number;
|
|
134
|
+
/** Distance ratio between successive LOD levels. Default 2. */
|
|
135
|
+
lodMultiplier?: number;
|
|
136
|
+
/** Cap on decoded chunk arrays cached on the CPU. Default by device memory. */
|
|
137
|
+
cpuCacheBytes?: number;
|
|
138
|
+
/**
|
|
139
|
+
* Foveation ramp for the `.rad` page-table frontier: detail is full inside
|
|
140
|
+
* `coneFov0` degrees of the view direction, falls off to `coneFoveate` by
|
|
141
|
+
* `coneFov`, and to `behindFoveate` directly behind the camera. Off-cone
|
|
142
|
+
* content is kept **coarse**, never dropped, so turning or zooming out never
|
|
143
|
+
* exposes an unpainted region. Defaults match Spark
|
|
144
|
+
* ({@link FRONTIER_FOVEATION_DEFAULTS}).
|
|
145
|
+
*/
|
|
146
|
+
frontierFoveation?: Partial<FrontierFoveation>;
|
|
147
|
+
/**
|
|
148
|
+
* Keeps a complete multi-run replacement hidden while its uploads are
|
|
149
|
+
* spread over frames, then switches the region atomically. Enabled by
|
|
150
|
+
* default; set `false` only for legacy A/B comparison.
|
|
151
|
+
*/
|
|
152
|
+
experimentalStagedSwaps?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Maximum splats copied into the pool per LOD mutation tick. Defaults to
|
|
155
|
+
* 32,000; lower debug values trade refinement latency for shorter frames.
|
|
156
|
+
*/
|
|
157
|
+
maxSplatsPerSwap?: number;
|
|
158
|
+
/**
|
|
159
|
+
* First-frame reveal policy for classic `.lcc` streaming.
|
|
160
|
+
*
|
|
161
|
+
* - `'progressive'`: cells become visible as each L0 coverage group commits
|
|
162
|
+
* - can show sparse near-detail while siblings load.
|
|
163
|
+
* - `'hold-near-l0'` (the default for classic `.lcc` when unset): hide the
|
|
164
|
+
* mesh until the camera's home coverage group is resident (L0 when it fits;
|
|
165
|
+
* otherwise coarsen via the leaf ladder L1→L2). Neighbours are not part of
|
|
166
|
+
* the hold - they compete via screenImportance and would steal the first
|
|
167
|
+
* fetch slots. Home selection uses distance within `lodBaseDistance` and
|
|
168
|
+
* does not require frustum intersection (HiRes tiles often fail `inView`
|
|
169
|
+
* when the camera stands inside looking out). Coarser rungs come from
|
|
170
|
+
* `LodSource.runsAtLevelFor`. Only home files are fetched during the hold.
|
|
171
|
+
* A one-minute watchdog also degrades if the cut cannot finish. Other
|
|
172
|
+
* streamed formats default to `'progressive'` and treat the hold as
|
|
173
|
+
* disabled.
|
|
174
|
+
*
|
|
175
|
+
* Does not make detail downloads instantaneous; it avoids lower-LOD traffic
|
|
176
|
+
* and multi-cell buildup before the first useful frame. The hold uses the
|
|
177
|
+
* **resolved** cut from the first schedule (after camera + format transform),
|
|
178
|
+
* not distance ambition alone.
|
|
179
|
+
*/
|
|
180
|
+
initialReveal?: 'progressive' | 'hold-near-l0';
|
|
181
|
+
/** Receives lightweight LOD swap markers for performance attribution. */
|
|
182
|
+
onPerformanceEvent?: (event: StreamedSplatPerformanceEvent) => void;
|
|
183
|
+
/**
|
|
184
|
+
* View-dependent color (higher-order SH). This is the streaming counterpart
|
|
185
|
+
* of {@link SplatMeshOptions.shBands}: besides sizing the pool it decides
|
|
186
|
+
* whether SH is fetched/decoded at all. Two sources feed it - a `Quality` LCC
|
|
187
|
+
* `Quality` LCC (`.lcc`) capture, which stores SH per splat, and a Streamed SOG scene,
|
|
188
|
+
* whose per-file palette shN is converted to that same packed form at decode
|
|
189
|
+
* (M11; see `docs/formats/streamed-shn-notes.md`).
|
|
190
|
+
*
|
|
191
|
+
* **For LCC, unset (the default) means every band the capture carries** - so
|
|
192
|
+
* a Quality scene shows its real view-dependent color without the caller
|
|
193
|
+
* having to know the format. The exception is a `smooth` performance profile
|
|
194
|
+
* (the default on mobile), which defaults this to 0: SH roughly triples
|
|
195
|
+
* per-chunk bandwidth (`shcoef.bin` is 64 B/splat against `data.bin`'s 32) and
|
|
196
|
+
* adds up to 64 B/splat of pool textures (~384 MB over a 6M-splat pool at 3
|
|
197
|
+
* bands) - precisely the costs that profile avoids.
|
|
198
|
+
*
|
|
199
|
+
* **For Streamed SOG it is strictly opt-in** (unset = off): the manifest does
|
|
200
|
+
* not declare whether the tiles carry shN, so enabling the conversion - and
|
|
201
|
+
* the pool textures it needs - must be a deliberate choice, not a default.
|
|
202
|
+
*
|
|
203
|
+
* Set it explicitly to override either way: 0 forces SH off, and 1, 2 or 3
|
|
204
|
+
* keep 3, 8 or 15 coefficients per channel. For LCC the value is clamped to
|
|
205
|
+
* what the scene actually has (a `Portable` capture fetches and allocates
|
|
206
|
+
* nothing regardless); for SOG a scene with fewer bands zero-pads and one with
|
|
207
|
+
* no shN simply renders DC color, wasting the allocated textures.
|
|
208
|
+
*
|
|
209
|
+
* Only read at load: the pool's SH textures are allocated once, so a later
|
|
210
|
+
* {@link SplatMesh.setPerformanceProfile} does not change this.
|
|
211
|
+
*/
|
|
212
|
+
shBands?: 0 | 1 | 2 | 3;
|
|
213
|
+
/**
|
|
214
|
+
* Whether the scene's always-resident environment/background tile (the
|
|
215
|
+
* `.lcc2` sky) starts visible. Default `true`. Toggle it live afterwards with
|
|
216
|
+
* {@link StreamedSplatMesh.setEnvironmentEnabled}. No effect on a scene that
|
|
217
|
+
* ships no environment tile.
|
|
218
|
+
*/
|
|
219
|
+
environmentEnabled?: boolean;
|
|
220
|
+
/**
|
|
221
|
+
* This mesh's share of the scene's fetch bandwidth, as a camera-projected
|
|
222
|
+
* weight - normally `() => governor.weightOf(mesh) ?? 0`, so fetching is
|
|
223
|
+
* ordered by the same measure that already orders drawing.
|
|
224
|
+
*
|
|
225
|
+
* Read on demand, so it always reflects the current camera. Zero means hidden
|
|
226
|
+
* or suspended, and has one effect on its own: the background sweep that
|
|
227
|
+
* pre-warms the whole capture into the page-table cache stops. That sweep is
|
|
228
|
+
* pure speculation about a camera move that has not happened, and on a
|
|
229
|
+
* multi-marker scene it is most of the traffic competing with the marker the
|
|
230
|
+
* viewer is actually looking at.
|
|
231
|
+
*
|
|
232
|
+
* Unset (the default) leaves fetching exactly as it was: every mesh sweeps.
|
|
233
|
+
* Supply a {@link fetchScheduler} as well to also bound the total.
|
|
234
|
+
*/
|
|
235
|
+
fetchWeight?: () => number;
|
|
236
|
+
/**
|
|
237
|
+
* Scene-wide fetch arbitration, shared by every streamed mesh the way a
|
|
238
|
+
* {@link SplatMeshOptions.pool} is - see {@link ChunkFetchScheduler}. Without
|
|
239
|
+
* one, each mesh fetches toward its own in-flight cap and a near marker's
|
|
240
|
+
* detail queues behind a dozen far markers' background traffic.
|
|
241
|
+
*
|
|
242
|
+
* The scheduler is *not* owned by the mesh: dispose unregisters this mesh and
|
|
243
|
+
* leaves the scheduler running for its siblings. Weights come from
|
|
244
|
+
* {@link fetchWeight}; without that every mesh weighs the same and the
|
|
245
|
+
* scheduler only bounds the total.
|
|
246
|
+
*/
|
|
247
|
+
fetchScheduler?: ChunkFetchScheduler;
|
|
248
|
+
/**
|
|
249
|
+
* Scene-wide decoded-chunk cache ceiling, shared exactly as
|
|
250
|
+
* {@link fetchScheduler} and {@link SplatMeshOptions.pool} are - see
|
|
251
|
+
* {@link ChunkCacheBudget}.
|
|
252
|
+
*
|
|
253
|
+
* Without one, each `.rad` page-table mesh caps its own cache at
|
|
254
|
+
* `max(cpuCacheBytes, min(2 GiB, this capture's decoded size))`: the right
|
|
255
|
+
* number for a lone streamed scene, and no bound at all across a scene of
|
|
256
|
+
* markers, because every mesh gets its own and each is sized to its own
|
|
257
|
+
* capture. With one, that figure becomes this mesh's *ceiling* and the budget
|
|
258
|
+
* splits a scene total across every registered mesh by camera weight.
|
|
259
|
+
*
|
|
260
|
+
* This bounds retention, not prefetching: the background sweep still runs and
|
|
261
|
+
* still warms the cache, it just stops at the scene's allowance instead of at
|
|
262
|
+
* the size of the capture.
|
|
263
|
+
*
|
|
264
|
+
* The budget is *not* owned by the mesh: dispose unregisters this mesh and
|
|
265
|
+
* leaves it running for its siblings. Weights come from {@link fetchWeight}.
|
|
266
|
+
*/
|
|
267
|
+
cacheBudget?: ChunkCacheBudget;
|
|
268
|
+
}
|
|
269
|
+
/** One streamed-LOD mutation tick, measured on the main thread. */
|
|
270
|
+
export interface StreamedSplatPerformanceEvent {
|
|
271
|
+
/** Timestamp after the tick, on the same clock as requestAnimationFrame. */
|
|
272
|
+
timestamp: number;
|
|
273
|
+
/** Main-thread time spent rescheduling and applying this tick. */
|
|
274
|
+
cpuMs: number;
|
|
275
|
+
/** Same-frame packed active-index rebuild time, after the LOD mutation. */
|
|
276
|
+
activeListMs: number;
|
|
277
|
+
/** Same-frame partial texture upload submission time. */
|
|
278
|
+
uploadMs: number;
|
|
279
|
+
/** CPU submission time for the depth-sort passes. */
|
|
280
|
+
sortSubmitMs: number;
|
|
281
|
+
/** Exact-height staging textures allocated during this update. */
|
|
282
|
+
stagingTextureAllocations: number;
|
|
283
|
+
/** WebGPU source-index ranges queued for upload before this tick's sort. */
|
|
284
|
+
activeListUpdateRanges: number;
|
|
285
|
+
appendedCount: number;
|
|
286
|
+
removedCount: number;
|
|
287
|
+
stagedCount: number;
|
|
288
|
+
uploadCount: number;
|
|
289
|
+
activeCount: number;
|
|
290
|
+
forcedSort: boolean;
|
|
291
|
+
compacted: boolean;
|
|
292
|
+
}
|
|
293
|
+
/** Options for {@link StreamedSplatMesh.definePersistentChannel}. */
|
|
294
|
+
export interface PersistentChannelOptions extends SplatChannelOptions {
|
|
295
|
+
/**
|
|
296
|
+
* Cap on the number of `(chunk, splat)` edits stored for this channel.
|
|
297
|
+
* Editing past the cap is dropped with a one-time warning. Default 1,000,000.
|
|
298
|
+
*/
|
|
299
|
+
maxEdits?: number;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Streams a large splat scene - a Streamed SOG dataset (`lod-meta.json`), or
|
|
303
|
+
* an XGRIDS `.lcc2` or `.lcc` (manifest v3–v5) dataset - into the pool of a
|
|
304
|
+
* dynamic-capacity {@link SplatMesh}, keeping the resident splat count within
|
|
305
|
+
* a per-device budget.
|
|
306
|
+
*
|
|
307
|
+
* Each frame it asks the scene's {@link LodSource} which spatial regions
|
|
308
|
+
* should be resident for the current camera, fetches and decodes the chunk
|
|
309
|
+
* files that back them (off the main thread, via {@link ChunkLoader}), and
|
|
310
|
+
* appends/removes pool ranges to match - loading coarse first so the scene
|
|
311
|
+
* appears quickly and refining near the camera. A coarse full-scene shell
|
|
312
|
+
* always fits the budget, so the view is never blank and the budget is
|
|
313
|
+
* never exceeded.
|
|
314
|
+
*
|
|
315
|
+
* WebGPU only (inherited from the dynamic-capacity pool). View-dependent color
|
|
316
|
+
* (higher-order SH) works for every streamed format: LCC `Quality` captures store it per
|
|
317
|
+
* splat, and a SOG scene's per-file palette shN is converted to that same
|
|
318
|
+
* per-splat packed form at decode so it too survives the shared pool (M11, opt
|
|
319
|
+
* in via {@link StreamedSplatMeshOptions.shBands}; see
|
|
320
|
+
* `docs/formats/streamed-shn-notes.md`).
|
|
321
|
+
*/
|
|
322
|
+
export declare class StreamedSplatMesh extends SplatMesh {
|
|
323
|
+
/**
|
|
324
|
+
* Loads a non-streamed splat file into a worker-built merged hierarchy.
|
|
325
|
+
* The returned static mesh shares this class's runtime budget contract.
|
|
326
|
+
*/
|
|
327
|
+
static loadAutoLod(input: string | URL, options: StaticLodSplatMeshLoadOptions): Promise<StaticLodSplatMesh>;
|
|
328
|
+
private readonly scene;
|
|
329
|
+
private readonly loader;
|
|
330
|
+
/**
|
|
331
|
+
* Cap the *classic* (non-page-table) chunk cache evicts against.
|
|
332
|
+
*
|
|
333
|
+
* Mutable because a shared {@link ChunkCacheBudget} re-splits it as the camera
|
|
334
|
+
* moves; without a budget it stays at the value `options.cpuCacheBytes` or the
|
|
335
|
+
* device default set at construction.
|
|
336
|
+
*/
|
|
337
|
+
private cpuCacheBytes;
|
|
338
|
+
private budgetValue;
|
|
339
|
+
private readonly maximumBudget;
|
|
340
|
+
/** Spark's per-mesh `lodScale`; divides the page-table cut limit. */
|
|
341
|
+
private lodScaleValue;
|
|
342
|
+
/** Set once the governed budget has been reported as exceeding an explicit
|
|
343
|
+
* `foveationDrawBudget`, so the warning is issued at most once. */
|
|
344
|
+
private warnedDrawTargetCap;
|
|
345
|
+
private readonly stagedSwapsEnabled;
|
|
346
|
+
/** Classic LCC must keep old cell coverage while a replacement is pending. */
|
|
347
|
+
private readonly neverRetireCoverageEarly;
|
|
348
|
+
private readonly appendCap;
|
|
349
|
+
private readonly onPerformanceEvent;
|
|
350
|
+
private compactionCount;
|
|
351
|
+
private readonly cache;
|
|
352
|
+
/** Running byte total of {@link cache}; maintained by {@link cacheChunk}, eviction and dispose. */
|
|
353
|
+
private cacheBytesTotal;
|
|
354
|
+
/** In-flight chunk fetches. The kind is kept so a weight change can shed the
|
|
355
|
+
* speculative ones without touching the detail that is actually on screen. */
|
|
356
|
+
private readonly fetching;
|
|
357
|
+
/** This mesh's camera-projected share of the scene's fetch bandwidth. */
|
|
358
|
+
private fetchWeight;
|
|
359
|
+
/** Scene-wide fetch arbitration, when the host shares one; see `requestChunk`. */
|
|
360
|
+
private readonly fetchScheduler;
|
|
361
|
+
private readonly fetchHandle;
|
|
362
|
+
/**
|
|
363
|
+
* Blob-URL dataset from {@link loadLocal}, owned by this mesh so its object
|
|
364
|
+
* URLs are revoked on {@link dispose} rather than leaking for the document's
|
|
365
|
+
* lifetime. Undefined for every network-loaded mesh.
|
|
366
|
+
*/
|
|
367
|
+
private localSource;
|
|
368
|
+
/** Scene-wide chunk-cache ceiling, when the host shares one. */
|
|
369
|
+
private readonly cacheBudget;
|
|
370
|
+
private cacheBudgetHandle;
|
|
371
|
+
/**
|
|
372
|
+
* The cap this mesh's frontier worker is currently evicting against.
|
|
373
|
+
*
|
|
374
|
+
* Mirrored on the main thread so `applyCacheAllowance` can skip no-op posts
|
|
375
|
+
* and so `fetchCounts.cacheLimitBytes` stays truthful between plans.
|
|
376
|
+
*/
|
|
377
|
+
private cacheLimitBytes;
|
|
378
|
+
private readonly resident;
|
|
379
|
+
/** Replacement runs hidden while their pool data is uploaded in bounded segments. */
|
|
380
|
+
private readonly staged;
|
|
381
|
+
/** Files awaiting a backoff retry after a transient fetch/decode error. */
|
|
382
|
+
private readonly retrying;
|
|
383
|
+
/** Files given up on after {@link MAX_CHUNK_ATTEMPTS} failures. */
|
|
384
|
+
private readonly failedFiles;
|
|
385
|
+
/**
|
|
386
|
+
* When true, each resident run writes its LOD `level` into the `lodLevel`
|
|
387
|
+
* float channel for false-color debug modifiers.
|
|
388
|
+
*/
|
|
389
|
+
private lodLevelDebug;
|
|
390
|
+
private lodLevelChannelReady;
|
|
391
|
+
private lodLevelScratch;
|
|
392
|
+
/** Desired-but-not-resident files this tick; protected from cache eviction. */
|
|
393
|
+
private readonly neededFiles;
|
|
394
|
+
/** Non-null in `foveationMode: 'pagetable'`: the worker that owns the chunk
|
|
395
|
+
* cache + traversal + pager off the main thread, and the always-active slab it
|
|
396
|
+
* pages the returned frontier into. */
|
|
397
|
+
private readonly frontierWorker;
|
|
398
|
+
/**
|
|
399
|
+
* The frontier's slots, as a list of equally sized pages rather than one
|
|
400
|
+
* contiguous run.
|
|
401
|
+
*
|
|
402
|
+
* Slot `i` lives in page `i / slabPageSplats` at offset `i %
|
|
403
|
+
* slabPageSplats`. The pager only ever addresses slots, so where those
|
|
404
|
+
* pages sit in the pool is the mesh's business - which is what lets a mesh
|
|
405
|
+
* hold non-contiguous storage, and ultimately lets several meshes interleave
|
|
406
|
+
* in one pool instead of each reserving its whole ceiling as one block.
|
|
407
|
+
* (Spark's pager does the same thing one level down, binding fixed pages to
|
|
408
|
+
* `(source, chunk)` pairs.)
|
|
409
|
+
*/
|
|
410
|
+
private readonly slabPages;
|
|
411
|
+
/** Most slots the slab may ever hold - the construction capacity. */
|
|
412
|
+
private slabCeiling;
|
|
413
|
+
/** Slot count the worker's pager was last told about. */
|
|
414
|
+
private pagerSlots;
|
|
415
|
+
/** Consecutive ticks the wave gate has held retirements back. */
|
|
416
|
+
private retireHeldTicks;
|
|
417
|
+
/** Backing store for {@link planTimings}. */
|
|
418
|
+
private readonly planTimingsValue;
|
|
419
|
+
/**
|
|
420
|
+
* Backing store for {@link fetchCounts}. Lifetime totals, because the question
|
|
421
|
+
* they answer is about a *steady state* - "this keeps streaming after the view
|
|
422
|
+
* settled" - which a per-frame or windowed number cannot express.
|
|
423
|
+
*/
|
|
424
|
+
private readonly fetchCountsValue;
|
|
425
|
+
/**
|
|
426
|
+
* The screen-radius band the scene asked for, kept so the band can be scaled
|
|
427
|
+
* with the solved frontier cut and always relative to the original - scaling
|
|
428
|
+
* the live values repeatedly would drift. Null when the scene has no band.
|
|
429
|
+
*/
|
|
430
|
+
private readonly frontierBandBase;
|
|
431
|
+
/**
|
|
432
|
+
* Splats per slab page for this mesh: {@link SLAB_PAGE_SPLATS}, or the whole
|
|
433
|
+
* capacity when that is smaller. Spark can use one fixed page size because
|
|
434
|
+
* its pool is a single large arena; here a mesh may be smaller than a page,
|
|
435
|
+
* and rounding it up to one would waste most of the reservation.
|
|
436
|
+
*/
|
|
437
|
+
private readonly slabPageSplats;
|
|
438
|
+
/** Target drawn-splat count for the page-table frontier; see the constructor. */
|
|
439
|
+
private pageTableDrawBudget;
|
|
440
|
+
/** The unclamped draw target (`foveationDrawBudget` or the default), kept so
|
|
441
|
+
* `setBudget` can re-derive the effective draw budget when the pool budget
|
|
442
|
+
* moves (e.g. under a `BudgetGovernor`). */
|
|
443
|
+
private pageTableDrawTarget;
|
|
444
|
+
/** Whether {@link pageTableDrawTarget} came from an explicit
|
|
445
|
+
* `foveationDrawBudget` - a caller-chosen hard cap worth warning about when a
|
|
446
|
+
* governed budget outgrows it, rather than the library's own default. */
|
|
447
|
+
private pageTableDrawTargetExplicit;
|
|
448
|
+
/** Last frontier's drawn (non-degenerate) splat count - the true on-screen size
|
|
449
|
+
* in `pagetable` mode, where the slab is fully "active" but mostly degenerate. */
|
|
450
|
+
private pageTableDrawn;
|
|
451
|
+
/** Monotonic reschedule id; a stale plan (superseded by a newer request) is
|
|
452
|
+
* dropped. `pageTableInFlight` coalesces to one outstanding traversal. */
|
|
453
|
+
private pageTableSeq;
|
|
454
|
+
private pageTableInFlight;
|
|
455
|
+
private pageTableDisposed;
|
|
456
|
+
/** Files whose data has been forwarded to the worker (so we don't refetch). */
|
|
457
|
+
private readonly pageTableCachedFiles;
|
|
458
|
+
/** Chunks the last frontier wanted but did not have, biggest-on-screen first.
|
|
459
|
+
* These outrank the background sweep - they are the detail actually on screen. */
|
|
460
|
+
private pageTableFetchPriority;
|
|
461
|
+
/** Frontier-cut target node size (px) and foveation ramp; see `frontierView`. */
|
|
462
|
+
private pageTableTargetPx;
|
|
463
|
+
private pageTableFoveation;
|
|
464
|
+
/** Drawing-buffer height, sampled in `update` so `reschedule` can derive the
|
|
465
|
+
* cut limit the same way the material does (`targetPx / focalY`). */
|
|
466
|
+
private pageTableViewportY;
|
|
467
|
+
/** Frontier cut on foveated `size / distance`. Re-derived each reschedule once
|
|
468
|
+
* the drawing buffer is known; the initial value only covers the first frame. */
|
|
469
|
+
private pageTableLimit;
|
|
470
|
+
/**
|
|
471
|
+
* Whether the worker's cache is sitting at its cap: sweeping past that point
|
|
472
|
+
* only evicts what the frontier is using.
|
|
473
|
+
*
|
|
474
|
+
* Re-derived from every plan rather than latched. It used to latch on the
|
|
475
|
+
* first eviction, which was safe only while the cap was sized to the capture
|
|
476
|
+
* and evictions therefore meant "this will never fit". Under a scene-wide
|
|
477
|
+
* {@link ChunkCacheBudget} evictions are routine - a far mesh gives bytes back
|
|
478
|
+
* and is trimmed - and latching would kill its sweep for the session, so a
|
|
479
|
+
* mesh that went cold could never re-warm when the camera returned.
|
|
480
|
+
*/
|
|
481
|
+
private pageTableCacheAtLimit;
|
|
482
|
+
/** Per-splat channels whose edits survive chunk eviction/reload (M7.6). */
|
|
483
|
+
private readonly persistentChannels;
|
|
484
|
+
/** Chunk-file index of the always-resident environment tile, if the scene ships one. */
|
|
485
|
+
private readonly envFile;
|
|
486
|
+
/** Whether the environment tile should be visible; toggled live. */
|
|
487
|
+
private envEnabled;
|
|
488
|
+
/** Pool handle of the environment tile once it has loaded (kept for toggling). */
|
|
489
|
+
private envHandle;
|
|
490
|
+
/** Env splat count, measured when the tile decodes; 0 until then. */
|
|
491
|
+
private envSplatCount;
|
|
492
|
+
/** Set when the env tile is larger than the whole pool - terminal, warned once. */
|
|
493
|
+
private envUnfit;
|
|
494
|
+
/**
|
|
495
|
+
* Classic `.lcc` startup hold. `'capture'` waits for the first schedule after
|
|
496
|
+
* the host applies the final camera; `'holding'` freezes that nearby-detail set.
|
|
497
|
+
*/
|
|
498
|
+
private initialRevealPhase;
|
|
499
|
+
/** Frozen nearby-detail runs for {@link initialRevealPhase} `'holding'`. */
|
|
500
|
+
private frozenCriticalRuns;
|
|
501
|
+
/** Timestamp of the final-camera capture that began the current hold. */
|
|
502
|
+
private initialRevealStartedAt;
|
|
503
|
+
private initialRevealStateValue;
|
|
504
|
+
/** Fetch settings this mesh was loaded with, reused for collision meshes. */
|
|
505
|
+
private readonly requestOptions;
|
|
506
|
+
/** In-flight or settled collision load; see {@link loadCollisionMeshes}. */
|
|
507
|
+
private collisionTiles;
|
|
508
|
+
private collisionAbort;
|
|
509
|
+
private pendingWork;
|
|
510
|
+
private lastScheduleTime;
|
|
511
|
+
/** Reused leaf-coverage bitmap for {@link substituteCoverage}; grows only. */
|
|
512
|
+
private coverageScratch;
|
|
513
|
+
private readonly lastCameraPos;
|
|
514
|
+
private readonly lastCameraQuat;
|
|
515
|
+
/**
|
|
516
|
+
* Fetches a scene manifest and prepares a mesh sized to the budget.
|
|
517
|
+
* Accepts a Streamed SOG manifest (`lod-meta.json`) or an XGRIDS `.lcc2` or
|
|
518
|
+
* `.lcc` (manifest v3–v5) dataset - all stream through the same machinery. Both LCC
|
|
519
|
+
* generations are normalized to the established XGRIDS/Spark Three.js
|
|
520
|
+
* coordinate frame; streamed SOG orientation is unchanged.
|
|
521
|
+
*
|
|
522
|
+
* A `.lcc` dataset needs a server that answers HTTP range requests: its
|
|
523
|
+
* splats live in one large `data.bin` that is never fetched whole.
|
|
524
|
+
*
|
|
525
|
+
* @param manifestUrl - URL of the scene's `lod-meta.json`, `.lcc2` or `.lcc`
|
|
526
|
+
* file; relative URLs resolve against `options.baseUrl` (or the page).
|
|
527
|
+
* @throws Rejects with {@link SplatLoadError} on any resolve/fetch/parse
|
|
528
|
+
* failure, or a `DOMException` named `AbortError` when `options.signal` fires.
|
|
529
|
+
*/
|
|
530
|
+
static load(manifestUrl: string | URL, options?: StreamedSplatMeshOptions): Promise<StreamedSplatMesh>;
|
|
531
|
+
/**
|
|
532
|
+
* Prepares a mesh from a folder dropped into the page - the same streamed
|
|
533
|
+
* formats, read straight off the user's disk with no server and no upload.
|
|
534
|
+
*
|
|
535
|
+
* Every file becomes a `blob:` URL, which answers range requests exactly as
|
|
536
|
+
* an HTTP origin does, so a multi-hundred-megabyte `.lcc` `data.bin` streams
|
|
537
|
+
* chunk-by-chunk rather than being read whole.
|
|
538
|
+
*
|
|
539
|
+
* @param files - The folder's files, keyed by path relative to its root
|
|
540
|
+
* (as the demo drop-zone `readDirectory` walk produces).
|
|
541
|
+
* @throws Rejects with {@link SplatLoadError} - phase `'manifest'` when the
|
|
542
|
+
* folder holds no (or more than one) recognizable scene manifest - or a
|
|
543
|
+
* `DOMException` named `AbortError` when `options.signal` fires.
|
|
544
|
+
*/
|
|
545
|
+
static loadLocal(files: ReadonlyMap<string, File>, options?: StreamedSplatMeshOptions): Promise<StreamedSplatMesh>;
|
|
546
|
+
/** Shared load path: fetch the manifest from a source, then build the scene. */
|
|
547
|
+
private static fromSource;
|
|
548
|
+
private constructor();
|
|
549
|
+
/** Slots the currently reserved pages can hold. */
|
|
550
|
+
private get slabSlots();
|
|
551
|
+
/**
|
|
552
|
+
* Reserves or releases slab pages so the slab can hold `wanted` slots, and
|
|
553
|
+
* tells the worker's pager the new slot count.
|
|
554
|
+
*
|
|
555
|
+
* This is the mechanism that makes a shared pool worth having: storage follows
|
|
556
|
+
* the governed budget, so approaching a marker grows its pages while the ones
|
|
557
|
+
* behind you hand theirs back, instead of every marker holding its ceiling for
|
|
558
|
+
* the whole session. Growth stops at the construction ceiling and at whatever
|
|
559
|
+
* the pool can actually spare - a mesh that cannot grow simply stays coarse
|
|
560
|
+
* rather than throwing.
|
|
561
|
+
*/
|
|
562
|
+
private syncSlabPages;
|
|
563
|
+
/**
|
|
564
|
+
* Writes `data` at slot `slot`, splitting the write where it crosses a page
|
|
565
|
+
* boundary. The pager's runs are contiguous in *slot* space, which page
|
|
566
|
+
* storage no longer guarantees is contiguous in the pool.
|
|
567
|
+
*/
|
|
568
|
+
private writeSlabSlots;
|
|
569
|
+
/** Zeros slots `[slot, slot + count)`, splitting at page boundaries like
|
|
570
|
+
* {@link writeSlabSlots}, so freed slots hold nothing drawable. */
|
|
571
|
+
private degenerateSlabSlots;
|
|
572
|
+
/**
|
|
573
|
+
* Draws exactly the first `resident` slots: pages below the boundary are
|
|
574
|
+
* fully active, the page containing it is partially active, the rest are
|
|
575
|
+
* inactive. Freed tail slots simply leave the active list; they are also
|
|
576
|
+
* degenerated (see {@link degenerateSlabSlots}) so that even a slot drawn by
|
|
577
|
+
* mistake shows nothing.
|
|
578
|
+
*/
|
|
579
|
+
private setSlabResident;
|
|
580
|
+
/** Typed post to the frontier worker. */
|
|
581
|
+
private postToWorker;
|
|
582
|
+
/**
|
|
583
|
+
* Applies a new decoded-chunk allowance from the scene's shared
|
|
584
|
+
* {@link ChunkCacheBudget}.
|
|
585
|
+
*
|
|
586
|
+
* Only the cap moves; nothing is dropped here. Both cache implementations
|
|
587
|
+
* evict lazily against it - the page-table worker inside its next
|
|
588
|
+
* `reschedule`, against a frontier that is still current, and the classic
|
|
589
|
+
* path in `evictChunks` on the next tick. Dropping chunks synchronously would
|
|
590
|
+
* pull them out from under resident splats.
|
|
591
|
+
*
|
|
592
|
+
* `pageTableCacheAtLimit` is recomputed here rather than waiting for the next
|
|
593
|
+
* plan, so a *raised* allowance re-arms the background sweep on this tick
|
|
594
|
+
* instead of one idle interval later.
|
|
595
|
+
*/
|
|
596
|
+
private applyCacheAllowance;
|
|
597
|
+
/**
|
|
598
|
+
* Whether this scene ships collision meshes - true for an XGRIDS `.lcc` /
|
|
599
|
+
* `.lcc2` dataset that carries them, false for a Streamed SOG scene, which
|
|
600
|
+
* has none.
|
|
601
|
+
*/
|
|
602
|
+
get hasCollisionMeshes(): boolean;
|
|
603
|
+
/**
|
|
604
|
+
* Fetches and parses this scene's collision geometry: the triangle meshes an
|
|
605
|
+
* XGRIDS `.lcc` (`collision.lci`) or `.lcc2` (`data/mesh/*.ply`) capture
|
|
606
|
+
* ships beside its splats, for hosts that want collision, ground probes or
|
|
607
|
+
* other spatial queries.
|
|
608
|
+
*
|
|
609
|
+
* The geometry is source-local, like {@link StreamedScene.bounds} - apply
|
|
610
|
+
* this mesh's `matrixWorld` to put it in the frame the splats render in.
|
|
611
|
+
* VLAM! builds no acceleration structure over it and never consults it.
|
|
612
|
+
*
|
|
613
|
+
* Tiles are fetched once and cached; concurrent callers share one load, and
|
|
614
|
+
* a failed load can be retried by calling again. Resolves `[]` for a scene
|
|
615
|
+
* without collision.
|
|
616
|
+
*
|
|
617
|
+
* @throws a `DOMException` named `AbortError` if cancelled, or if
|
|
618
|
+
* {@link dispose} is called while the load is in flight.
|
|
619
|
+
*/
|
|
620
|
+
loadCollisionMeshes(options?: {
|
|
621
|
+
signal?: AbortSignal;
|
|
622
|
+
}): Promise<readonly CollisionMeshTile[]>;
|
|
623
|
+
/**
|
|
624
|
+
* Whether this scene ships an always-resident environment/background tile -
|
|
625
|
+
* true for an XGRIDS `.lcc2` capture that carries one (its `env.sog` sky),
|
|
626
|
+
* false for Streamed SOG, `.lcc`, `.rad`, or an `.lcc2` without one.
|
|
627
|
+
*/
|
|
628
|
+
get hasEnvironment(): boolean;
|
|
629
|
+
/** Whether the environment tile is currently set to render. */
|
|
630
|
+
get environmentEnabled(): boolean;
|
|
631
|
+
/**
|
|
632
|
+
* Splats in the environment tile, measured when it decoded - the manifest
|
|
633
|
+
* does not carry the count. 0 until the tile has loaded (or if the scene
|
|
634
|
+
* ships none). These sit outside the LOD budget, drawing from the pool's
|
|
635
|
+
* capacity headroom.
|
|
636
|
+
*/
|
|
637
|
+
get environmentSplatCount(): number;
|
|
638
|
+
/**
|
|
639
|
+
* Shows or hides the scene's environment/background tile. The switch is
|
|
640
|
+
* instant and never refetches: once loaded, the tile stays in the pool and
|
|
641
|
+
* only its active flag flips. Enabling before the tile has loaded triggers
|
|
642
|
+
* its (one-time) load on the next update. No-op on a scene without one.
|
|
643
|
+
*/
|
|
644
|
+
setEnvironmentEnabled(enabled: boolean): void;
|
|
645
|
+
/** The active-splat budget this mesh keeps within. */
|
|
646
|
+
get budget(): number;
|
|
647
|
+
/**
|
|
648
|
+
* The ceiling {@link setBudget} clamps to - {@link StreamedSplatMeshOptions.maxBudget}
|
|
649
|
+
* when one was given, otherwise the construction budget.
|
|
650
|
+
*
|
|
651
|
+
* The pool was allocated for this number and cannot grow, so it is a hard
|
|
652
|
+
* limit on what any governor can hand this mesh. Read it to check that a
|
|
653
|
+
* shared-budget setup can actually deliver the share it is computing.
|
|
654
|
+
*/
|
|
655
|
+
get maxBudget(): number;
|
|
656
|
+
/** Alias used by hosts that manage static and streamed auto-LOD uniformly. */
|
|
657
|
+
get budgetCeiling(): number;
|
|
658
|
+
/**
|
|
659
|
+
* The capture's real content size, when the format declares it (`.rad` reports
|
|
660
|
+
* its leaf count) - the splat count needed to hold this mesh at full
|
|
661
|
+
* resolution, independent of the budget it was constructed with.
|
|
662
|
+
*
|
|
663
|
+
* A host splitting one budget across several streamed meshes should clamp each
|
|
664
|
+
* share to this: a mesh cannot spend more than it contains, so budget handed
|
|
665
|
+
* past it buys nothing and is better given to a mesh that can use it. Note it
|
|
666
|
+
* is *not* `maxBudget`: a foveated `.rad` reports `maxResidentSplats` as the
|
|
667
|
+
* requested budget, because its pool holds a camera-directed resident set
|
|
668
|
+
* rather than the whole tree.
|
|
669
|
+
*
|
|
670
|
+
* `undefined` when the format does not declare a content size.
|
|
671
|
+
*/
|
|
672
|
+
get contentSplatCount(): number | undefined;
|
|
673
|
+
/**
|
|
674
|
+
* The drawn-splat target currently driving the `.rad` page-table frontier -
|
|
675
|
+
* the governed budget, capped by
|
|
676
|
+
* {@link SplatMeshOptions.foveationDrawBudget}. `0` on a mesh that is not in
|
|
677
|
+
* `foveationMode: 'pagetable'`, which has no frontier to target.
|
|
678
|
+
*
|
|
679
|
+
* This is the number that decides how deep the traversal descends, so it is
|
|
680
|
+
* what to watch when checking that a near mesh really did receive more
|
|
681
|
+
* detail: {@link budget} is the pool's allowance, this is what is spent.
|
|
682
|
+
*/
|
|
683
|
+
get drawBudget(): number;
|
|
684
|
+
/**
|
|
685
|
+
* Spark's per-mesh `lodScale` (see {@link StreamedSplatMeshOptions.lodScale}).
|
|
686
|
+
* Mutable: raise it to sharpen a focused mesh, lower it to coarsen a
|
|
687
|
+
* background one. Page-table `.rad` only.
|
|
688
|
+
*
|
|
689
|
+
* @throws {RangeError} if set to a value that is not positive and finite.
|
|
690
|
+
*/
|
|
691
|
+
get lodScale(): number;
|
|
692
|
+
set lodScale(value: number);
|
|
693
|
+
/** In `pagetable` mode the slab is fully active but mostly degenerate, so the
|
|
694
|
+
* base `activeSplatCount` (slab size) is not the on-screen count - report the
|
|
695
|
+
* frontier's drawn size instead. */
|
|
696
|
+
get activeSplatCount(): number;
|
|
697
|
+
/**
|
|
698
|
+
* Updates the LOD budget used for future scheduling within allocated capacity.
|
|
699
|
+
*
|
|
700
|
+
* @returns the budget actually in effect, which is `budget` clamped to
|
|
701
|
+
* {@link maxBudget}. A `BudgetGovernor` reads this return value to detect a
|
|
702
|
+
* capped member and hand the remainder to the others, so the clamp is
|
|
703
|
+
* reported rather than hidden.
|
|
704
|
+
*/
|
|
705
|
+
setBudget(budget: number): number;
|
|
706
|
+
/**
|
|
707
|
+
* What the LOD scheduler last decided, or `undefined` on sources that do not
|
|
708
|
+
* schedule by leaf (the `.rad` page table, prefix readers).
|
|
709
|
+
*
|
|
710
|
+
* Distinct from {@link activeSplatCount}, and the distinction is the whole
|
|
711
|
+
* point: `desired` is what the scheduler asked for, `activeSplatCount` is
|
|
712
|
+
* what the pool ended up drawing. Equal means the cut is applied; `desired`
|
|
713
|
+
* far below the budget means the *scheduler* declined to spend it, which is a
|
|
714
|
+
* different bug from the mesh failing to apply what it was given.
|
|
715
|
+
*/
|
|
716
|
+
get lodStats(): Readonly<{
|
|
717
|
+
inFrustum: number;
|
|
718
|
+
leaves: number;
|
|
719
|
+
desired: number;
|
|
720
|
+
filled: number;
|
|
721
|
+
}> | undefined;
|
|
722
|
+
/** Number of chunk files currently decoded and held. In page-table mode the
|
|
723
|
+
* worker owns the cache - the main-thread map is always empty there, so report
|
|
724
|
+
* what has been forwarded to it instead of a permanent zero. */
|
|
725
|
+
get residentChunkCount(): number;
|
|
726
|
+
/** Chunk fetches currently in flight. */
|
|
727
|
+
get pendingChunkCount(): number;
|
|
728
|
+
/**
|
|
729
|
+
* Main-thread cost of applying paging plans in `foveationMode: 'pagetable'`.
|
|
730
|
+
*
|
|
731
|
+
* A plan is applied whole, off the render loop's own timing, so its cost does
|
|
732
|
+
* not appear in {@link getUpdateTimings} - but it lands on the same thread and
|
|
733
|
+
* a churning frontier can make it the largest stall in a frame. `worst*`
|
|
734
|
+
* accumulate over the mesh's lifetime; the rest describe the most recent plan.
|
|
735
|
+
*/
|
|
736
|
+
get planTimings(): Readonly<{
|
|
737
|
+
applyMs: number;
|
|
738
|
+
worstApplyMs: number;
|
|
739
|
+
writeMs: number;
|
|
740
|
+
residentMs: number;
|
|
741
|
+
moves: number;
|
|
742
|
+
appends: number;
|
|
743
|
+
worstSplats: number;
|
|
744
|
+
}>;
|
|
745
|
+
/**
|
|
746
|
+
* Lifetime chunk-fetch totals by kind, plus page-table cache state.
|
|
747
|
+
*
|
|
748
|
+
* Diagnostic for the question "why is this still streaming after the view
|
|
749
|
+
* settled?", which the three fetch sources answer differently and which no
|
|
750
|
+
* other reading distinguishes:
|
|
751
|
+
*
|
|
752
|
+
* - **`sweep` climbing** - speculative file-order pre-warming of the whole
|
|
753
|
+
* capture. Declined by the `smooth` profile; see `sweepAllowed`.
|
|
754
|
+
* - **`priority` / `base` climbing while `evicted` climbs too** - the
|
|
755
|
+
* frontier's touched set does not fit the worker cache, so chunks are
|
|
756
|
+
* evicted and immediately refetched. Streaming never ends because it cannot.
|
|
757
|
+
* - **`priority` / `base` climbing with `evicted` flat** - ordinary refinement
|
|
758
|
+
* still converging on the cut; it should stop on its own.
|
|
759
|
+
*
|
|
760
|
+
* `uncovered` and `retiredEarly` answer a different question - "why are there
|
|
761
|
+
* holes?" - and between them cover both ways this class can render nothing
|
|
762
|
+
* where it should render something:
|
|
763
|
+
*
|
|
764
|
+
* - **`uncovered` climbing after the scene settles** - `substituteCoverage`
|
|
765
|
+
* wanted a leaf's coarsest level as a stand-in and its chunk was not
|
|
766
|
+
* cached. Expected briefly during initial load; afterwards it should not
|
|
767
|
+
* move, because the coarsest files are pinned against eviction.
|
|
768
|
+
* - **`retiredEarly` climbing** - coverage was retired before its replacement
|
|
769
|
+
* landed, under pool pressure or past the retirement hold bound. This is
|
|
770
|
+
* the swap path rather than the substitute path, and it is the one that
|
|
771
|
+
* scales with the budget.
|
|
772
|
+
*
|
|
773
|
+
* Both are counted in whole leaves/groups, monotonically: they answer "did
|
|
774
|
+
* this happen, and is it still happening", not "how much is missing now".
|
|
775
|
+
*/
|
|
776
|
+
get fetchCounts(): Readonly<{
|
|
777
|
+
priority: number;
|
|
778
|
+
base: number;
|
|
779
|
+
sweep: number;
|
|
780
|
+
evicted: number;
|
|
781
|
+
uncovered: number;
|
|
782
|
+
retiredEarly: number;
|
|
783
|
+
cacheFull: boolean;
|
|
784
|
+
cacheBytes: number;
|
|
785
|
+
cacheLimitBytes: number;
|
|
786
|
+
}>;
|
|
787
|
+
/** Chunk files given up on after repeated fetch/decode failures. */
|
|
788
|
+
get failedChunkCount(): number;
|
|
789
|
+
/**
|
|
790
|
+
* Forgets all permanent chunk failures so their regions are fetched again.
|
|
791
|
+
* Failures are otherwise terminal for the mesh's lifetime - call this when
|
|
792
|
+
* the cause was transient (e.g. connectivity restored, `online` event).
|
|
793
|
+
*/
|
|
794
|
+
retryFailedChunks(): void;
|
|
795
|
+
/**
|
|
796
|
+
* Whether the scene is still resolving toward its target detail - chunks
|
|
797
|
+
* are fetching, or a retry/append is pending. Goes false once the view
|
|
798
|
+
* has settled (useful to drive a loading indicator).
|
|
799
|
+
*/
|
|
800
|
+
get isStreaming(): boolean;
|
|
801
|
+
/** The LOD distance model (mutable; e.g. raise to force the finest level). */
|
|
802
|
+
get lodBaseDistance(): number;
|
|
803
|
+
set lodBaseDistance(value: number);
|
|
804
|
+
update(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, options?: SplatUpdateOptions): void;
|
|
805
|
+
/** Root bounds of the whole scene, valid before any chunk has loaded. */
|
|
806
|
+
computeSplatBounds(): THREE.Box3;
|
|
807
|
+
/**
|
|
808
|
+
* Enables or disables writing each resident run's **resolved** LOD level into
|
|
809
|
+
* the `lodLevel` float channel (for a false-color debug modifier: 0 = finest).
|
|
810
|
+
* Values come from applied desired runs after budget resolution - not from
|
|
811
|
+
* distance ambition alone. Call before assigning a modifier that reads the channel.
|
|
812
|
+
*/
|
|
813
|
+
setLodLevelDebug(enabled: boolean): void;
|
|
814
|
+
/** Whether {@link setLodLevelDebug} is currently writing levels. */
|
|
815
|
+
get isLodLevelDebug(): boolean;
|
|
816
|
+
/**
|
|
817
|
+
* Startup-hold progress for {@link StreamedSplatMeshOptions.initialReveal}.
|
|
818
|
+
* Classic `.lcc` hosts using the default (or explicitly opting into
|
|
819
|
+
* `'hold-near-l0'`) should keep the mesh invisible while
|
|
820
|
+
* `status === 'pending'`, then reveal on `'ready'` or `'degraded'`.
|
|
821
|
+
*/
|
|
822
|
+
get initialRevealState(): InitialRevealState;
|
|
823
|
+
/**
|
|
824
|
+
* Captures a fresh classic `.lcc` nearby-detail startup set on the next
|
|
825
|
+
* {@link update}. Hosts that apply their final initial camera pose after the
|
|
826
|
+
* mesh first receives frames should call this before lifting their loading
|
|
827
|
+
* cover. It is a no-op for every other format and for progressive startup.
|
|
828
|
+
*/
|
|
829
|
+
recaptureInitialReveal(): void;
|
|
830
|
+
private writeLodLevelChannel;
|
|
831
|
+
/**
|
|
832
|
+
* Declares a per-splat channel whose values **persist across LOD churn**:
|
|
833
|
+
* edits are stored sparsely keyed by `(chunk file, local index)` - a stable
|
|
834
|
+
* splat identity in the streaming design - and re-applied whenever a chunk
|
|
835
|
+
* is (re)appended. Paint a region with {@link paintPersistent}, orbit away
|
|
836
|
+
* until it is evicted, come back, and the values return. See M7.6.
|
|
837
|
+
*
|
|
838
|
+
* Wraps {@link SplatMesh.defineChannel}; read it from a modifier with
|
|
839
|
+
* `ctx.channel(name)` as usual.
|
|
840
|
+
*/
|
|
841
|
+
definePersistentChannel(name: string, options?: PersistentChannelOptions): void;
|
|
842
|
+
/**
|
|
843
|
+
* Sets a persistent channel to `value` for every currently-resident splat
|
|
844
|
+
* within `radius` (world units) of `worldPoint`, and records the edit so it
|
|
845
|
+
* survives eviction/reload. Splats whose chunk is not currently decoded on
|
|
846
|
+
* the CPU cannot be located and are skipped (they are usually far from the
|
|
847
|
+
* camera); their painted neighbours in resident chunks are unaffected.
|
|
848
|
+
*
|
|
849
|
+
* The radius assumes this mesh's world transform is rigid (rotation +
|
|
850
|
+
* translation, as the built-in format transforms are); a scaled mesh would
|
|
851
|
+
* distort the brush. Edit a persistent channel only through this method -
|
|
852
|
+
* direct {@link SplatMesh.writeChannel} writes are not recorded and are
|
|
853
|
+
* overwritten by the next re-apply.
|
|
854
|
+
*
|
|
855
|
+
* @returns the number of splats edited this call.
|
|
856
|
+
* @throws {Error} if the channel was not declared with
|
|
857
|
+
* {@link definePersistentChannel}.
|
|
858
|
+
*/
|
|
859
|
+
paintPersistent(name: string, worldPoint: THREE.Vector3, radius: number, value: number): number;
|
|
860
|
+
/**
|
|
861
|
+
* Clears every stored edit for a persistent channel and zeroes the value on
|
|
862
|
+
* all currently-resident splats. Chunks that are not resident are covered by
|
|
863
|
+
* the emptied store - they reload at the channel's fill value.
|
|
864
|
+
*
|
|
865
|
+
* @throws {Error} if the channel is not a persistent channel.
|
|
866
|
+
*/
|
|
867
|
+
clearPersistentChannel(name: string): void;
|
|
868
|
+
dispose(): void;
|
|
869
|
+
private shouldReschedule;
|
|
870
|
+
private reschedule;
|
|
871
|
+
private rowAlignedSplats;
|
|
872
|
+
/**
|
|
873
|
+
* Startup hold seeds: the coverage group containing (or nearest to) the
|
|
874
|
+
* camera within {@link LodSource.lodBaseDistance}. HiRes tiles often fail the
|
|
875
|
+
* frustum test when most of the cell sits behind the camera - do **not**
|
|
876
|
+
* require `inView`, or the hold seeds a screen-facing neighbour instead.
|
|
877
|
+
* Coarser home levels come from {@link LodSource.runsAtLevelFor}.
|
|
878
|
+
*/
|
|
879
|
+
private selectHomeSeedRuns;
|
|
880
|
+
private coarsenHomeRuns;
|
|
881
|
+
private criticalRunsFitCapacity;
|
|
882
|
+
private publishInitialRevealProgress;
|
|
883
|
+
private releaseInitialReveal;
|
|
884
|
+
private captureOrContinueInitialReveal;
|
|
885
|
+
/** After staging/commits, release the hold when every frozen run is resident. */
|
|
886
|
+
private finishInitialRevealIfComplete;
|
|
887
|
+
/** Creates a marker for a changed streamed-LOD tick, if any. */
|
|
888
|
+
private createPerformanceEvent;
|
|
889
|
+
/** Returns whether all new rows can coexist with the currently visible region. */
|
|
890
|
+
private canStageGroup;
|
|
891
|
+
/**
|
|
892
|
+
* Uploads a bounded part of a replacement without rendering it.
|
|
893
|
+
* Skips runs whose chunks are not yet cached so siblings can stage out of order.
|
|
894
|
+
*/
|
|
895
|
+
private stageGroup;
|
|
896
|
+
/** Switches a fully staged region from old to new visibility in one tick. */
|
|
897
|
+
private commitStagedGroup;
|
|
898
|
+
/**
|
|
899
|
+
* Applies one swap group atomically within this tick: removals first
|
|
900
|
+
* (freeing pool rows for the replacements), then all adds. Returns false
|
|
901
|
+
* without touching anything when the group cannot fit even after its own
|
|
902
|
+
* removals - the caller defers it and the old runs keep rendering.
|
|
903
|
+
*/
|
|
904
|
+
private applyGroup;
|
|
905
|
+
/** Appends one run from the cache, compacting the pool on fragmentation. */
|
|
906
|
+
private appendRun;
|
|
907
|
+
/**
|
|
908
|
+
* Writes the stored edits for one run's `[offset, offset + count)` splats
|
|
909
|
+
* into its freshly appended pool range. No-op when the file has no edits.
|
|
910
|
+
*/
|
|
911
|
+
private applyPersistentRun;
|
|
912
|
+
/**
|
|
913
|
+
* Covers a deferred group's leaves that no resident run covers with each
|
|
914
|
+
* leaf's coarsest (pinned, hence cached) level, so a region waiting on a
|
|
915
|
+
* fetch shows coarse detail instead of nothing. The substitutes are
|
|
916
|
+
* intentionally not "desired": the next reschedule swaps them for the
|
|
917
|
+
* real level once its chunk has arrived.
|
|
918
|
+
*
|
|
919
|
+
* Near-camera refinements (`distance <= lodBaseDistance`) skip the coarse
|
|
920
|
+
* paint and its pin fetch entirely - cold load requests only the target cut
|
|
921
|
+
* for that cell. Far gaps keep the shell.
|
|
922
|
+
*/
|
|
923
|
+
private substituteCoverage;
|
|
924
|
+
/** Issues pending classic-path chunk wants in group-priority order. */
|
|
925
|
+
private flushClassicFetches;
|
|
926
|
+
/**
|
|
927
|
+
* A camera turn must not wait for all eight old visible requests to finish.
|
|
928
|
+
* Only classic requests carry a precise rank; page-table work retains its
|
|
929
|
+
* own scheduler and is never cancelled here.
|
|
930
|
+
*/
|
|
931
|
+
private preemptClassicFetches;
|
|
932
|
+
/**
|
|
933
|
+
* Loads the always-resident environment tile once, on the first update after
|
|
934
|
+
* it is wanted. The tile has no LOD ladder and no manifest count, so it is
|
|
935
|
+
* appended whole (measuring its splat count at decode) and thereafter toggled
|
|
936
|
+
* by flipping its pool range active - never scheduled, refetched, or evicted.
|
|
937
|
+
*/
|
|
938
|
+
private updateEnvironment;
|
|
939
|
+
/**
|
|
940
|
+
* Page-table reschedule (`foveationMode: 'pagetable'`): posts the camera to the
|
|
941
|
+
* worker, which owns the cache + traversal + pager and replies asynchronously
|
|
942
|
+
* with a paging plan. Coalesced to one outstanding request so the main thread
|
|
943
|
+
* never blocks. Also drives chunk fetching, in priority order.
|
|
944
|
+
*/
|
|
945
|
+
private reschedulePageTable;
|
|
946
|
+
/**
|
|
947
|
+
* Applies a paging plan from the worker to the slab - fast memcpy writes only,
|
|
948
|
+
* no traversal or gather on the main thread - then fetches the chunks the
|
|
949
|
+
* frontier wants next, and reschedules again if chunks are still streaming.
|
|
950
|
+
*/
|
|
951
|
+
private applyFrontierPlan;
|
|
952
|
+
/** Forwards a decoded chunk's arrays to the worker (buffers transferred) so the
|
|
953
|
+
* worker's cache/traversal/gather can use it. */
|
|
954
|
+
private forwardChunkToWorker;
|
|
955
|
+
/** Parallel chunk fetches. HTTP/2 multiplexes them; on HTTP/1.1 the browser's
|
|
956
|
+
* per-host cap simply queues. Same cap for classic and pagetable so near
|
|
957
|
+
* detail is not structurally starved on the non-pagetable path. */
|
|
958
|
+
private get maxInflight();
|
|
959
|
+
/**
|
|
960
|
+
* Whether this mesh may run its speculative background sweep. A mesh with no
|
|
961
|
+
* weight is hidden or suspended; a mesh with no `fetchWeight` at all is a
|
|
962
|
+
* host that never asked for arbitration, and keeps the old behaviour.
|
|
963
|
+
*/
|
|
964
|
+
/**
|
|
965
|
+
* Sets this mesh's share of the scene's fetch bandwidth, as
|
|
966
|
+
* {@link StreamedSplatMeshOptions.fetchWeight} does at load.
|
|
967
|
+
*
|
|
968
|
+
* The weight normally closes over the mesh itself (`() =>
|
|
969
|
+
* governor.weightOf(mesh)`), which a host cannot express until `load`
|
|
970
|
+
* resolves - hence a setter as well as an option. Pass `undefined` to go back
|
|
971
|
+
* to unarbitrated sweeping.
|
|
972
|
+
*/
|
|
973
|
+
setFetchWeight(weight: (() => number) | undefined): void;
|
|
974
|
+
private sweepAllowed;
|
|
975
|
+
/** Aborts in-flight fetches of one kind; their slots return through `finally`. */
|
|
976
|
+
private abortFetches;
|
|
977
|
+
private requestChunk;
|
|
978
|
+
/**
|
|
979
|
+
* Stores a decoded chunk while keeping {@link cacheBytesTotal} in step. The
|
|
980
|
+
* counter replaces a full-cache re-sum on every reschedule; every mutation
|
|
981
|
+
* of {@link cache} (this method, eviction, dispose's clear) maintains it.
|
|
982
|
+
*/
|
|
983
|
+
private cacheChunk;
|
|
984
|
+
private evictChunks;
|
|
985
|
+
}
|