rayzee 7.31.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +164 -0
  2. package/dist/rayzee.es.js +6728 -4735
  3. package/dist/rayzee.umd.js +121 -73
  4. package/package.json +2 -2
  5. package/src/EngineDefaults.js +168 -13
  6. package/src/EngineIssues.js +5 -0
  7. package/src/PathTracerApp.js +151 -25
  8. package/src/Processor/ArchiveReader.js +538 -0
  9. package/src/Processor/AssetLoader.js +265 -23
  10. package/src/Processor/BVHBuilder.js +48 -32
  11. package/src/Processor/BVHRefitter.js +244 -99
  12. package/src/Processor/ChunkedRecords.js +302 -0
  13. package/src/Processor/EmissiveTriangleBuilder.js +48 -21
  14. package/src/Processor/GeometryExtractor.js +580 -93
  15. package/src/Processor/HostMemory.js +222 -0
  16. package/src/Processor/InstanceTable.js +548 -97
  17. package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
  18. package/src/Processor/PBRT/PBRTCurves.js +249 -0
  19. package/src/Processor/PBRT/PBRTMath.js +19 -0
  20. package/src/Processor/PBRT/PBRTParser.js +187 -41
  21. package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
  22. package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
  23. package/src/Processor/PBRT/index.js +257 -50
  24. package/src/Processor/PackedRayBuffer.js +29 -18
  25. package/src/Processor/ParallelBVHBuilder.js +9 -8
  26. package/src/Processor/QueueManager.js +5 -3
  27. package/src/Processor/SceneProcessor.js +1046 -365
  28. package/src/Processor/TLASBuilder.js +546 -201
  29. package/src/Processor/TextureCreator.js +11 -9
  30. package/src/Processor/VRAMTracker.js +12 -3
  31. package/src/Processor/Workers/BVHRefitWorker.js +16 -11
  32. package/src/Processor/Workers/BVHWorker.js +12 -12
  33. package/src/Processor/Workers/TLASWorker.js +37 -0
  34. package/src/Processor/utils.js +8 -20
  35. package/src/Stages/NormalDepth.js +6 -2
  36. package/src/Stages/PathTracer.js +5 -5
  37. package/src/Stages/PathTracerStage.js +163 -41
  38. package/src/TSL/BVHTraversal.js +208 -90
  39. package/src/TSL/Common.js +135 -2
  40. package/src/TSL/Displacement.js +21 -11
  41. package/src/TSL/EmissiveSampling.js +49 -25
  42. package/src/TSL/ExtendKernel.js +3 -2
  43. package/src/TSL/LightBVHSampling.js +7 -4
  44. package/src/TSL/LightsDirect.js +30 -29
  45. package/src/TSL/ShadeKernel.js +8 -6
  46. package/src/TSL/SortGlobalKernels.js +3 -4
  47. package/src/TSL/Struct.js +3 -0
  48. package/src/TSL/TextureSampling.js +24 -9
  49. package/src/TSL/patches.js +149 -84
  50. package/src/index.js +3 -0
  51. package/src/managers/AnimationManager.js +86 -76
  52. package/src/managers/MaterialDataManager.js +29 -23
  53. package/src/managers/TransformManager.js +9 -192
@@ -0,0 +1,222 @@
1
+ /**
2
+ * HostMemory.js — CPU-side counterpart to VRAMTracker: what a scene will cost in JS memory,
3
+ * whether this session can still place it, and where the peak actually went.
4
+ *
5
+ * The wall at this scale is not RAM, it is contiguous *address space*: a 40M-triangle Moana
6
+ * needs ~7.3 GB of ArrayBuffer and a freshly booted renderer can place ~7.0-7.7 GB in 64 MB
7
+ * pieces, so the same build loads after a reboot and fails after hours of uptime.
8
+ *
9
+ * ⚠️ A probe is only cheap when it is small. On an idle page, 8.6 GB of 64 MB buffers is placed
10
+ * and released in 102 ms and never shows up as resident memory. Run the same probe while the
11
+ * parse still holds 3.6 GB and the renderer goes to 9.2 GB resident and the load takes twice as
12
+ * long — the probe's own peak is added to the build's. So the up-front check is an estimate
13
+ * against a known ceiling, with no allocation at all, and the probe is saved for the one step
14
+ * that actually runs out (BVH assembly), where it asks only for that step's bytes.
15
+ */
16
+
17
+ import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
18
+ import { DEFAULT_CHUNK_BYTES } from './ChunkedRecords.js';
19
+
20
+ /** Bytes one stored triangle occupies in the triangle records. */
21
+ const TRIANGLE_BYTES = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE * 4;
22
+
23
+ /** Bytes per BVH node: 16 floats, 4 vec4s. */
24
+ const BVH_NODE_BYTES = 64;
25
+
26
+ /**
27
+ * BVH nodes per stored triangle, measured on Moana: 35.0M triangles → 29.93M nodes (0.855),
28
+ * 40.0M → 33.20M (0.830). Rounded up, because under-estimating is the failure that matters.
29
+ */
30
+ const NODES_PER_TRIANGLE = 0.9;
31
+
32
+ /** Bytes per placement in the instance table: a mat4 of world transform. */
33
+ const PLACEMENT_BYTES = 64;
34
+
35
+ /** Bytes per triangle of per-template reorder map, one Uint32 each. */
36
+ const ORDER_MAP_BYTES = 4;
37
+
38
+ /** Below this a load cannot plausibly hit the wall, so it is not worth remarking on. */
39
+ export const PREFLIGHT_MIN_BYTES = 1536 * 1024 * 1024;
40
+
41
+ /**
42
+ * What a freshly started renderer could place in 64 MB pieces, measured: 7,040 MB. A scene
43
+ * estimated above this needs a session with its address space still unfragmented, which in
44
+ * practice means a recently restarted browser.
45
+ *
46
+ * Deliberately not a hard limit — it moves with host uptime, and a scene over it still loads
47
+ * on a clean boot. It is the line above which a warning is honest.
48
+ */
49
+ export const SAFE_SCENE_BYTES = 7040 * 1024 * 1024;
50
+
51
+ /**
52
+ * The line past which the build refuses rather than tries.
53
+ *
54
+ * Above this the failure stops being a catchable `Array buffer allocation failed` and becomes a
55
+ * dead renderer process — measured on Moana: 40M (7.3 GB) and 45M (8.5 GB) both load, 50M
56
+ * (~9.7 GB) takes the tab down at 9.4 GB resident with nothing thrown and nothing logged. There
57
+ * is no degrading gracefully past that point, so the only useful behaviour is to say why in
58
+ * advance. Raise it with `new SceneProcessor( { maxSceneBytes } )` to test a bigger rung.
59
+ */
60
+ export const MAX_SCENE_BYTES = 9216 * 1024 * 1024;
61
+
62
+ /** Headroom a single build step wants over its own size before it is called safe. */
63
+ export const PREFLIGHT_SAFETY = 1.15;
64
+
65
+ /** Never probe beyond this, however large the estimate. */
66
+ const PROBE_CEILING_BYTES = 12 * 1024 * 1024 * 1024;
67
+
68
+ /**
69
+ * Byte cost of the CPU structures a scene of this shape will hold once built.
70
+ *
71
+ * `geometryBytes` is the three.js geometry the caller already owns — it is not allocated by the
72
+ * build, but it stays resident for picking, transforms and raster, so it counts against the same
73
+ * address space.
74
+ *
75
+ * @param {{triangles: number, placements: number, geometryBytes?: number}} scene
76
+ * @returns {{total: number, triangles: number, bvh: number, geometry: number,
77
+ * placements: number, orderMaps: number}} bytes
78
+ */
79
+ export function estimateSceneBytes( { triangles, placements, geometryBytes = 0 } ) {
80
+
81
+ const parts = {
82
+ triangles: triangles * TRIANGLE_BYTES,
83
+ bvh: Math.ceil( triangles * NODES_PER_TRIANGLE ) * BVH_NODE_BYTES,
84
+ geometry: geometryBytes,
85
+ placements: placements * PLACEMENT_BYTES,
86
+ orderMaps: triangles * ORDER_MAP_BYTES,
87
+ };
88
+
89
+ parts.total = parts.triangles + parts.bvh + parts.geometry + parts.placements + parts.orderMaps;
90
+ return parts;
91
+
92
+ }
93
+
94
+ /**
95
+ * How many bytes this process can still place in chunk-sized pieces, right now.
96
+ *
97
+ * Allocates untouched buffers, so no physical page is committed and the probe costs
98
+ * milliseconds; they are released in reverse so the address space is handed back in the same
99
+ * shape it was taken. Stops at `wantBytes` — a probe only has to answer "is there room for
100
+ * this scene", and probing to exhaustion on every load would be both slow and rude.
101
+ *
102
+ * @param {number} wantBytes
103
+ * @param {{chunkBytes?: number, shared?: boolean, allocate?: function(number): Object}} [options]
104
+ * `allocate` exists so the failure path can be tested; nothing in the engine passes it.
105
+ * @returns {{placed: number, want: number, exhausted: boolean, capped: boolean}} `exhausted`
106
+ * means the allocator gave up first; `capped` means the request was larger than this function
107
+ * will ever probe, so a non-exhausted result proves only that `want` fits, not `wantBytes`.
108
+ */
109
+ export function probeAddressSpace( wantBytes, {
110
+ chunkBytes = DEFAULT_CHUNK_BYTES,
111
+ shared = false,
112
+ allocate = null,
113
+ } = {} ) {
114
+
115
+ const want = Math.min( wantBytes, PROBE_CEILING_BYTES );
116
+ const alloc = allocate ?? ( shared ? n => new SharedArrayBuffer( n ) : n => new ArrayBuffer( n ) );
117
+ const chunks = [];
118
+ let placed = 0;
119
+
120
+ try {
121
+
122
+ while ( placed < want ) {
123
+
124
+ const n = Math.min( chunkBytes, want - placed );
125
+ chunks.push( alloc( n ) );
126
+ placed += n;
127
+
128
+ }
129
+
130
+ } catch {
131
+
132
+ // An allocation failure is the answer, not an error.
133
+
134
+ }
135
+
136
+ // Reverse, so the allocator unwinds the same order it filled.
137
+ for ( let i = chunks.length - 1; i >= 0; i -- ) chunks[ i ] = null;
138
+ chunks.length = 0;
139
+
140
+ return { placed, want, exhausted: placed < want, capped: wantBytes > want };
141
+
142
+ }
143
+
144
+ /**
145
+ * Phase-tagged record of what the build allocated and what stayed live.
146
+ *
147
+ * Chunk allocations are pushed in by {@link ChunkedRecords} through its observer hook, so the
148
+ * ledger sees every chunk without the storage knowing anything about it. Frees are not
149
+ * observable — a chunk goes away when the last reference does — so `live` comes from explicit
150
+ * {@link MemoryLedger#sample} calls at phase boundaries, where the build knows what it is still
151
+ * holding. Cumulative allocation and live peak answer different questions and both are kept.
152
+ */
153
+ export class MemoryLedger {
154
+
155
+ constructor() {
156
+
157
+ this.reset();
158
+
159
+ }
160
+
161
+ reset() {
162
+
163
+ this.phase = 'idle';
164
+ this.events = [];
165
+ this.samples = [];
166
+ this.allocatedBytes = 0;
167
+ this.peakLiveBytes = 0;
168
+ this.byPhase = Object.create( null );
169
+
170
+ }
171
+
172
+ /** Name the phase that subsequent allocations belong to. */
173
+ mark( phase ) {
174
+
175
+ this.phase = phase;
176
+ this.byPhase[ phase ] ??= { allocated: 0, chunks: 0 };
177
+ return this;
178
+
179
+ }
180
+
181
+ /** Record an allocation of `bytes` attributed to the current phase. */
182
+ alloc( bytes, what = 'chunk' ) {
183
+
184
+ if ( ! ( bytes > 0 ) ) return;
185
+
186
+ const bucket = ( this.byPhase[ this.phase ] ??= { allocated: 0, chunks: 0 } );
187
+ bucket.allocated += bytes;
188
+ bucket.chunks ++;
189
+ this.allocatedBytes += bytes;
190
+ this.events.push( { phase: this.phase, bytes, what, at: performance.now() } );
191
+
192
+ }
193
+
194
+ /**
195
+ * Record what is live right now. The build passes the sizes it can actually account for;
196
+ * anything it has already dropped is simply absent, which is the point.
197
+ * @param {string} label
198
+ * @param {Object<string, number>} parts - byte size per named structure
199
+ */
200
+ sample( label, parts ) {
201
+
202
+ let total = 0;
203
+ for ( const k in parts ) total += parts[ k ] || 0;
204
+ if ( total > this.peakLiveBytes ) this.peakLiveBytes = total;
205
+ this.samples.push( { label, phase: this.phase, total, parts, at: performance.now() } );
206
+ return total;
207
+
208
+ }
209
+
210
+ /** A plain object for logging or handing to a host. */
211
+ get report() {
212
+
213
+ return {
214
+ allocatedBytes: this.allocatedBytes,
215
+ peakLiveBytes: this.peakLiveBytes,
216
+ byPhase: { ...this.byPhase },
217
+ samples: this.samples.slice(),
218
+ };
219
+
220
+ }
221
+
222
+ }