rayzee 8.8.2 → 9.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 (104) hide show
  1. package/README.md +210 -20
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10795 -5324
  6. package/dist/rayzee.umd.js +279 -268
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +60 -76
  10. package/src/EngineEvents.js +7 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/Passes/OIDNTemporalHistory.js +25 -7
  16. package/src/PathTracerApp.js +313 -25
  17. package/src/Processor/ArchiveCache.js +120 -0
  18. package/src/Processor/ArchiveReader.js +179 -32
  19. package/src/Processor/AssetLoader.js +523 -76
  20. package/src/Processor/AtmosphereModel.js +465 -0
  21. package/src/Processor/BVHBuilder.js +4 -1
  22. package/src/Processor/BVHLeafFold.js +100 -0
  23. package/src/Processor/BVHRefitter.js +98 -214
  24. package/src/Processor/ChunkedRecords.js +189 -4
  25. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  26. package/src/Processor/GeometryExtractor.js +238 -40
  27. package/src/Processor/InstanceTable.js +55 -37
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +210 -55
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +49 -8
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/MotionVector.js +8 -42
  48. package/src/Stages/NRD.js +30 -21
  49. package/src/Stages/NormalDepth.js +12 -23
  50. package/src/Stages/PathTracer.js +114 -6
  51. package/src/Stages/PathTracerStage.js +251 -104
  52. package/src/Storage/BLASCache.js +190 -0
  53. package/src/Storage/CDFCache.js +77 -0
  54. package/src/Storage/DownloadCache.js +266 -0
  55. package/src/Storage/GeometrySpill.js +217 -0
  56. package/src/Storage/SceneGraphCodec.js +503 -0
  57. package/src/Storage/SpillStore.js +112 -0
  58. package/src/Storage/StorageManager.js +934 -0
  59. package/src/Storage/StorageOps.js +137 -0
  60. package/src/Storage/StorageWorker.js +37 -0
  61. package/src/Storage/events.js +29 -0
  62. package/src/Storage/identity.js +77 -0
  63. package/src/Storage/index.js +9 -0
  64. package/src/Storage/inlineTransport.js +24 -0
  65. package/src/Storage/locks.js +99 -0
  66. package/src/Storage/openStorage.js +87 -0
  67. package/src/Storage/sceneCachePolicy.js +12 -0
  68. package/src/Storage/shared.js +9 -0
  69. package/src/Storage/transport.js +62 -0
  70. package/src/TSL/Atmosphere.js +684 -0
  71. package/src/TSL/BVHTraversal.js +288 -239
  72. package/src/TSL/CameraRay.js +99 -14
  73. package/src/TSL/Common.js +11 -2
  74. package/src/TSL/DebugKernel.js +2 -2
  75. package/src/TSL/Displacement.js +6 -6
  76. package/src/TSL/EmissiveSampling.js +5 -5
  77. package/src/TSL/EnvironmentCDF.js +176 -0
  78. package/src/TSL/GenerateKernel.js +5 -5
  79. package/src/TSL/HitFacet.js +4 -4
  80. package/src/TSL/LightsDirect.js +8 -8
  81. package/src/TSL/LightsSampling.js +55 -37
  82. package/src/TSL/Random.js +1 -0
  83. package/src/TSL/ShadeKernel.js +75 -19
  84. package/src/TSL/Sun.js +40 -0
  85. package/src/TSL/TextureSampling.js +6 -7
  86. package/src/index.js +22 -0
  87. package/src/managers/CameraManager.js +412 -56
  88. package/src/managers/DenoisingManager.js +4 -3
  89. package/src/managers/EnvironmentManager.js +263 -33
  90. package/src/managers/GoboManager.js +19 -8
  91. package/src/managers/IESManager.js +3 -3
  92. package/src/managers/InteractionManager.js +30 -2
  93. package/src/managers/LightManager.js +98 -5
  94. package/src/managers/MaterialDataManager.js +22 -2
  95. package/src/managers/TransformManager.js +26 -0
  96. package/src/managers/UniformManager.js +11 -7
  97. package/src/managers/VideoRenderManager.js +38 -17
  98. package/src/managers/ViewCamera.js +77 -0
  99. package/src/managers/WalkControls.js +262 -0
  100. package/src/managers/helpers/OutlineHelper.js +22 -2
  101. package/src/managers/timeline/CameraTrack.js +235 -0
  102. package/src/managers/timeline/TimelineManager.js +146 -0
  103. package/src/version.js +3 -0
  104. package/src/Processor/ProceduralSky.js +0 -291
@@ -0,0 +1,249 @@
1
+ import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
2
+ import { rebaseNodes } from './BVHLeafFold.js';
3
+
4
+ const FLOATS_PER_NODE = 16;
5
+ const A = TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET;
6
+ const B = TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET;
7
+ const C = TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET;
8
+
9
+ /**
10
+ * Sorts records [start, start + count) in place into spatial pieces of at most `pieceTriangles`,
11
+ * halving at the centroid median along the longest axis — so a mesh too large to build at once
12
+ * becomes pieces that build one per worker and join under a few nodes ({@link joinPieces}).
13
+ * @param {import('./ChunkedRecords.js').ChunkedRecords} records
14
+ * @param {{pause?: function(): Promise}} [options] - awaited every million records moved or so
15
+ * @returns {Promise<{order: Uint32Array, pieces: Array<{start: number, count: number}>, tree: Object}>}
16
+ * `order[ j ]` is the index, within the range, that record j held before; piece starts are
17
+ * relative to `start`, and `tree` is the halving as `{ left, right }` down to `{ piece }`.
18
+ */
19
+ export async function partitionRange( records, start, count, pieceTriangles, { pause = null } = {} ) {
20
+
21
+ const lanes = records.lanesPerRecord;
22
+ // One array per axis, swapped in step with `order`, so every pass reads memory in sequence.
23
+ const axes = [ new Float32Array( count ), new Float32Array( count ), new Float32Array( count ) ];
24
+ const [ cx, cy, cz ] = axes;
25
+ for ( let j = 0; j < count; ) {
26
+
27
+ const chunk = records.chunkFor( start + j );
28
+ const f = new Float32Array( chunk.buffer, chunk.byteOffset, chunk.length );
29
+ const end = Math.min( count, ( ( ( ( start + j ) / records.recordsPerChunk ) | 0 ) + 1 ) * records.recordsPerChunk - start );
30
+ for ( let d = records.baseOf( start + j ); j < end; j ++, d += lanes ) {
31
+
32
+ cx[ j ] = f[ d + A ] + f[ d + B ] + f[ d + C ];
33
+ cy[ j ] = f[ d + A + 1 ] + f[ d + B + 1 ] + f[ d + C + 1 ];
34
+ cz[ j ] = f[ d + A + 2 ] + f[ d + B + 2 ] + f[ d + C + 2 ];
35
+
36
+ }
37
+
38
+ }
39
+
40
+ const order = new Uint32Array( count );
41
+ for ( let j = 0; j < count; j ++ ) order[ j ] = j;
42
+
43
+ const pieces = [];
44
+ const split = ( lo, hi ) => {
45
+
46
+ if ( hi - lo <= pieceTriangles ) {
47
+
48
+ pieces.push( { start: lo, count: hi - lo } );
49
+ return { piece: pieces.length - 1 };
50
+
51
+ }
52
+
53
+ const mid = lo + ( ( hi - lo ) >> 1 );
54
+ selectNth( order, axes, longestAxis( axes, lo, hi ), lo, hi, mid );
55
+ return { left: split( lo, mid ), right: split( mid, hi ) };
56
+
57
+ };
58
+
59
+ const tree = split( 0, count );
60
+ await pause?.();
61
+ await permuteInPlace( records, start, count, order, pause );
62
+ return { order, pieces, tree };
63
+
64
+ }
65
+
66
+ function longestAxis( axes, lo, hi ) {
67
+
68
+ let best = 0, widest = - 1;
69
+ for ( let a = 0; a < 3; a ++ ) {
70
+
71
+ const c = axes[ a ];
72
+ let min = Infinity, max = - Infinity;
73
+ for ( let i = lo; i < hi; i ++ ) {
74
+
75
+ const v = c[ i ];
76
+ if ( v < min ) min = v;
77
+ if ( v > max ) max = v;
78
+
79
+ }
80
+
81
+ if ( max - min > widest ) {
82
+
83
+ widest = max - min;
84
+ best = a;
85
+
86
+ }
87
+
88
+ }
89
+
90
+ return best;
91
+
92
+ }
93
+
94
+ // Quickselect with a three-way partition, so runs of equal keys cannot make it quadratic, and
95
+ // pivots sampled with a fixed seed: the first, middle and last of a terrain grid's repeating rows
96
+ // took a thousand passes. Fixed, so the same mesh always builds the same BVH.
97
+ function selectNth( order, axes, axis, lo, hi, k ) {
98
+
99
+ const key = axes[ axis ];
100
+ const [ c0, c1, c2 ] = axes;
101
+ const swap = ( i, j ) => {
102
+
103
+ let t = order[ i ]; order[ i ] = order[ j ]; order[ j ] = t;
104
+ t = c0[ i ]; c0[ i ] = c0[ j ]; c0[ j ] = t;
105
+ t = c1[ i ]; c1[ i ] = c1[ j ]; c1[ j ] = t;
106
+ t = c2[ i ]; c2[ i ] = c2[ j ]; c2[ j ] = t;
107
+
108
+ };
109
+
110
+ let seed = 0x9e3779b9;
111
+ const sample = () => {
112
+
113
+ seed ^= seed << 13;
114
+ seed ^= seed >>> 17;
115
+ seed ^= seed << 5;
116
+ return key[ lo + ( seed >>> 0 ) % ( hi - lo ) ];
117
+
118
+ };
119
+
120
+ while ( hi - lo > 1 ) {
121
+
122
+ const a = sample(), b = sample(), c = sample();
123
+ const pivot = a < b ? ( b < c ? b : a < c ? c : a ) : ( a < c ? a : b < c ? c : b );
124
+
125
+ let lt = lo, i = lo, gt = hi;
126
+ while ( i < gt ) {
127
+
128
+ const v = key[ i ];
129
+ if ( v < pivot ) swap( lt ++, i ++ );
130
+ else if ( v > pivot ) swap( i, -- gt );
131
+ else i ++;
132
+
133
+ }
134
+
135
+ if ( k < lt ) hi = lt;
136
+ else if ( k >= gt ) lo = gt;
137
+ else return;
138
+
139
+ }
140
+
141
+ }
142
+
143
+ // Record j takes record order[ j ], following each cycle with one record held aside.
144
+ async function permuteInPlace( records, start, count, order, pause ) {
145
+
146
+ const lanes = records.lanesPerRecord;
147
+ const held = new Uint32Array( lanes );
148
+ const done = new Uint8Array( count );
149
+ let moved = 0;
150
+
151
+ for ( let j = 0; j < count; j ++ ) {
152
+
153
+ if ( done[ j ] ) continue;
154
+ done[ j ] = 1;
155
+ if ( order[ j ] === j ) continue;
156
+
157
+ let chunk = records.chunkFor( start + j ), base = records.baseOf( start + j );
158
+ for ( let l = 0; l < lanes; l ++ ) held[ l ] = chunk[ base + l ];
159
+
160
+ for ( let k = j; ; ) {
161
+
162
+ const s = order[ k ];
163
+ const to = chunk, at = base;
164
+ if ( s === j ) {
165
+
166
+ for ( let l = 0; l < lanes; l ++ ) to[ at + l ] = held[ l ];
167
+ break;
168
+
169
+ }
170
+
171
+ chunk = records.chunkFor( start + s );
172
+ base = records.baseOf( start + s );
173
+ for ( let l = 0; l < lanes; l ++ ) to[ at + l ] = chunk[ base + l ];
174
+ done[ s ] = 1;
175
+ k = s;
176
+ if ( pause && ( ++ moved & 0xfffff ) === 0 ) await pause();
177
+
178
+ }
179
+
180
+ }
181
+
182
+ }
183
+
184
+ /**
185
+ * One BLAS from the pieces of {@link partitionRange}: the halving becomes inner nodes ahead of
186
+ * the pieces' own nodes, which are rebased in place to follow them, so children always sit after
187
+ * their parent as a refit requires. Handed back as parts, never one array the size of the mesh:
188
+ * a 600 MB request is what failed an 80M-triangle build with memory to spare.
189
+ * @param {Object} tree
190
+ * @param {Array<{start: number, count: number}>} pieces
191
+ * @param {Array<{bvhData: Float32Array, originalToBvh: ?Uint32Array, aabb: ArrayLike<number>}>} built - per piece
192
+ * @param {Uint32Array} order - from partitionRange
193
+ * @returns {{bvhData: Float32Array[], nodeCount: number, originalToBvh: Uint32Array}} the join's nodes
194
+ * first, then each piece's; `originalToBvh` as one build of the whole range returns it
195
+ */
196
+ export function joinPieces( tree, pieces, built, order ) {
197
+
198
+ const inner = pieces.length - 1;
199
+ const bases = [];
200
+ let total = inner;
201
+ for ( const { bvhData } of built ) {
202
+
203
+ bases.push( total );
204
+ total += bvhData.length / FLOATS_PER_NODE;
205
+
206
+ }
207
+
208
+ const data = new Float32Array( inner * FLOATS_PER_NODE );
209
+ const idx = new Uint32Array( data.buffer );
210
+ const parts = [ data ];
211
+ const originalToBvh = new Uint32Array( order.length );
212
+
213
+ for ( let k = 0; k < pieces.length; k ++ ) {
214
+
215
+ const { bvhData, originalToBvh: pieceOrder } = built[ k ];
216
+ built[ k ].bvhData = built[ k ].originalToBvh = null;
217
+ const base = bases[ k ];
218
+ const { start, count } = pieces[ k ];
219
+ rebaseNodes( new Uint32Array( bvhData.buffer, bvhData.byteOffset, bvhData.length ), base, start );
220
+
221
+ parts.push( bvhData );
222
+ for ( let i = 0; i < count; i ++ ) originalToBvh[ order[ start + i ] ] = start + ( pieceOrder ? pieceOrder[ i ] : i );
223
+
224
+ }
225
+
226
+ let next = 0;
227
+ const place = node => {
228
+
229
+ if ( node.piece !== undefined ) return { index: bases[ node.piece ], box: built[ node.piece ].aabb };
230
+
231
+ const index = next ++;
232
+ const l = place( node.left );
233
+ const r = place( node.right );
234
+ const o = index * FLOATS_PER_NODE;
235
+ data[ o ] = l.box[ 0 ]; data[ o + 1 ] = l.box[ 1 ]; data[ o + 2 ] = l.box[ 2 ]; idx[ o + 3 ] = l.index;
236
+ data[ o + 4 ] = l.box[ 3 ]; data[ o + 5 ] = l.box[ 4 ]; data[ o + 6 ] = l.box[ 5 ]; idx[ o + 7 ] = r.index;
237
+ data[ o + 8 ] = r.box[ 0 ]; data[ o + 9 ] = r.box[ 1 ]; data[ o + 10 ] = r.box[ 2 ];
238
+ data[ o + 12 ] = r.box[ 3 ]; data[ o + 13 ] = r.box[ 4 ]; data[ o + 14 ] = r.box[ 5 ];
239
+
240
+ const box = [ 0, 1, 2 ].map( a => Math.min( l.box[ a ], r.box[ a ] ) )
241
+ .concat( [ 3, 4, 5 ].map( a => Math.max( l.box[ a ], r.box[ a ] ) ) );
242
+ return { index, box };
243
+
244
+ };
245
+
246
+ place( tree );
247
+ return { bvhData: parts, nodeCount: total, originalToBvh };
248
+
249
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Where the sun stands, from local solar time, day of the year and latitude. Declination is
3
+ * Spencer's (1971) series; azimuth is clockwise from north. Degrees in and out.
4
+ */
5
+
6
+ const RAD = Math.PI / 180;
7
+
8
+ function declination( dayOfYear ) {
9
+
10
+ const g = 2 * Math.PI * ( dayOfYear - 1 ) / 365;
11
+ return 0.006918 - 0.399912 * Math.cos( g ) + 0.070257 * Math.sin( g ) - 0.006758 * Math.cos( 2 * g )
12
+ + 0.000907 * Math.sin( 2 * g ) - 0.002697 * Math.cos( 3 * g ) + 0.00148 * Math.sin( 3 * g );
13
+
14
+ }
15
+
16
+ /** @returns {{ azimuth: number, elevation: number }} */
17
+ export function sunPosition( { hours, dayOfYear, latitude } ) {
18
+
19
+ const d = declination( dayOfYear );
20
+ const h = ( hours - 12 ) * 15 * RAD;
21
+ const lat = latitude * RAD;
22
+ const sinEl = Math.sin( lat ) * Math.sin( d ) + Math.cos( lat ) * Math.cos( d ) * Math.cos( h );
23
+ const elevation = Math.asin( Math.min( 1, Math.max( - 1, sinEl ) ) ) / RAD;
24
+ const azimuth = ( Math.atan2( Math.sin( h ), Math.cos( h ) * Math.sin( lat ) - Math.tan( d ) * Math.cos( lat ) ) / RAD + 540 ) % 360;
25
+ return { azimuth, elevation };
26
+
27
+ }
28
+
29
+ /** The solar time the sun reaches `elevation`, before or after noon; noon or midnight when it never does. */
30
+ export function timeForSunElevation( { elevation, dayOfYear, latitude, afternoon = false } ) {
31
+
32
+ const d = declination( dayOfYear );
33
+ const lat = latitude * RAD;
34
+ const cosH = ( Math.sin( elevation * RAD ) - Math.sin( lat ) * Math.sin( d ) ) / ( Math.cos( lat ) * Math.cos( d ) );
35
+ const h = Math.acos( Math.min( 1, Math.max( - 1, cosH ) ) ) / RAD / 15;
36
+ return afternoon ? 12 + h : 12 - h;
37
+
38
+ }
39
+
40
+ /** Day of the year of the 21st of `month` (1–12), near each solstice and equinox. */
41
+ export const dayOfYearForMonth = month => [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ][ month - 1 ] + 21;
@@ -251,6 +251,13 @@ class SmartBufferPool {
251
251
 
252
252
  }
253
253
 
254
+ /** Stops counting a buffer that is being let go rather than returned. */
255
+ forget( buffer ) {
256
+
257
+ this.memoryUsage = Math.max( 0, this.memoryUsage - buffer.buffer.byteLength );
258
+
259
+ }
260
+
254
261
  releaseBuffer( buffer, Type = Float32Array ) {
255
262
 
256
263
  // Recover the full allocated size from the underlying ArrayBuffer
@@ -1407,6 +1414,20 @@ export class TextureCreator {
1407
1414
 
1408
1415
  // Enhanced disposal
1409
1416
  texture.userData = { buffer: data, bufferType: Uint8Array };
1417
+
1418
+ // Nothing reads these pixels once they are on the GPU — a rebuild packs new arrays from the
1419
+ // three.js sources — so the CPU copy goes as soon as three.js has uploaded it. Not back to
1420
+ // the pool, which would keep it alive; a cache lookup then sees `buffer: null` and rebuilds.
1421
+ texture.onUpdate = () => {
1422
+
1423
+ const buffer = texture.userData.buffer;
1424
+ if ( ! buffer ) return;
1425
+ this.bufferPool.forget( buffer );
1426
+ texture.userData.buffer = null;
1427
+ texture.image.data = null;
1428
+
1429
+ };
1430
+
1410
1431
  const originalDispose = texture.dispose.bind( texture );
1411
1432
  texture.dispose = () => {
1412
1433
 
@@ -14,12 +14,16 @@
14
14
  * { type: 'error', message, id? }
15
15
  */
16
16
 
17
+ import { openInlineStorage } from '../../Storage/StorageManager.js';
18
+ import { DownloadCache } from '../../Storage/DownloadCache.js';
19
+
17
20
  // Asset config supplied via 'load' message from the main thread.
18
21
  // Defaults match the upstream Rayzee deployment; override via configureAssets().
19
22
  let _assetConfig = {
20
23
  ortRuntimeUrl: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs',
21
24
  ortWasmPaths: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/',
22
25
  cacheNamespace: 'rayzee',
26
+ storage: 'auto',
23
27
  };
24
28
 
25
29
  let ort = null;
@@ -38,98 +42,120 @@ async function getOrt() {
38
42
 
39
43
  }
40
44
 
41
- // IndexedDB names are namespaced so multiple consumers on the same origin don't collide.
42
- function getIdbName() {
45
+ let session = null;
46
+ let currentModelUrl = null;
47
+ let downloads = null;
43
48
 
44
- return `${_assetConfig.cacheNamespace}:ai-upscaler-models`;
49
+ // ─── Model cache (the engine's download storage, written from this worker) ─────
45
50
 
46
- }
51
+ function getDownloads() {
47
52
 
48
- const IDB_STORE = 'models';
53
+ downloads ??= ( async () => {
49
54
 
50
- let session = null;
51
- let currentModelUrl = null;
55
+ if ( _assetConfig.storage === false || ! navigator.storage?.getDirectory ) return new DownloadCache( null );
52
56
 
53
- // ─── IndexedDB Model Cache ───────────────────────────────────────────────────
57
+ try {
54
58
 
55
- function openDB() {
59
+ const top = await navigator.storage.getDirectory();
60
+ const root = await top.getDirectoryHandle( _assetConfig.cacheNamespace, { create: true } );
61
+ const storage = openInlineStorage( { namespace: _assetConfig.cacheNamespace, root } );
62
+ await migrateIndexedDB( storage ).catch( () => {} );
63
+ return new DownloadCache( storage );
56
64
 
57
- return new Promise( ( resolve, reject ) => {
65
+ } catch {
58
66
 
59
- const req = indexedDB.open( getIdbName(), 1 );
60
- req.onupgradeneeded = () => req.result.createObjectStore( IDB_STORE );
61
- req.onsuccess = () => resolve( req.result );
62
- req.onerror = () => reject( req.error );
67
+ return new DownloadCache( null );
63
68
 
64
- } );
69
+ }
70
+
71
+ } )();
72
+
73
+ return downloads;
65
74
 
66
75
  }
67
76
 
68
- async function getCachedModel( url ) {
77
+ // Earlier builds kept models in IndexedDB: move them across once, then drop the database.
78
+ async function migrateIndexedDB( storage ) {
69
79
 
70
- try {
80
+ const name = `${_assetConfig.cacheNamespace}:ai-upscaler-models`;
81
+ if ( typeof indexedDB === 'undefined' || ! indexedDB.databases ) return;
82
+ if ( ! ( await indexedDB.databases() ).some( ( d ) => d.name === name ) ) return;
71
83
 
72
- const db = await openDB();
73
- return await new Promise( ( resolve, reject ) => {
84
+ const entries = await new Promise( ( resolve, reject ) => {
74
85
 
75
- const tx = db.transaction( IDB_STORE, 'readonly' );
76
- const req = tx.objectStore( IDB_STORE ).get( url );
77
- req.onsuccess = () => resolve( req.result || null );
78
- req.onerror = () => reject( req.error );
86
+ const open = indexedDB.open( name );
87
+ open.onerror = () => reject( open.error );
88
+ open.onsuccess = () => {
79
89
 
80
- } );
90
+ const db = open.result;
91
+ if ( ! db.objectStoreNames.contains( 'models' ) ) {
81
92
 
82
- } catch {
93
+ db.close();
94
+ resolve( [] );
95
+ return;
83
96
 
84
- return null;
97
+ }
85
98
 
86
- }
99
+ const out = [];
100
+ const cursor = db.transaction( 'models', 'readonly' ).objectStore( 'models' ).openCursor();
101
+ cursor.onerror = () => reject( cursor.error );
102
+ cursor.onsuccess = () => {
87
103
 
88
- }
104
+ const c = cursor.result;
105
+ if ( ! c ) {
89
106
 
90
- async function cacheModel( url, buffer ) {
107
+ db.close();
108
+ resolve( out );
109
+ return;
91
110
 
92
- try {
111
+ }
112
+
113
+ out.push( [ c.key, c.value ] );
114
+ c.continue();
93
115
 
94
- const db = await openDB();
95
- await new Promise( ( resolve, reject ) => {
116
+ };
96
117
 
97
- const tx = db.transaction( IDB_STORE, 'readwrite' );
98
- tx.objectStore( IDB_STORE ).put( buffer, url );
99
- tx.oncomplete = () => resolve();
100
- tx.onerror = () => reject( tx.error );
118
+ };
101
119
 
102
- } );
120
+ } );
121
+
122
+ const area = storage.area( 'downloads' );
123
+ for ( const [ url, buffer ] of entries ) {
103
124
 
104
- } catch {
125
+ if ( typeof url !== 'string' || ! ( buffer instanceof ArrayBuffer ) || await area.has( url ) ) continue;
126
+ const writer = await area.create( url, { label: url.split( '/' ).pop(), expectedBytes: buffer.byteLength } );
127
+ if ( ! writer ) continue;
128
+ await writer.write( 'data', buffer, { transfer: true } );
129
+ await writer.commit( { url, contentType: 'application/octet-stream', checkedAt: Date.now() } );
105
130
 
106
- // Cache write failure is non-fatal
107
131
  }
108
132
 
133
+ await new Promise( ( resolve ) => {
134
+
135
+ const request = indexedDB.deleteDatabase( name );
136
+ request.onsuccess = request.onerror = request.onblocked = () => resolve();
137
+
138
+ } );
139
+
109
140
  }
110
141
 
111
142
  // ─── Model Loading ───────────────────────────────────────────────────────────
112
143
 
113
144
  async function fetchModel( url ) {
114
145
 
115
- // Try IndexedDB cache first
116
- const cached = await getCachedModel( url );
117
- if ( cached ) {
146
+ const { file, release, fromCache } = await ( await getDownloads() ).fetch( url );
118
147
 
119
- console.log( `AI Upscaler Worker: model loaded from cache (${( cached.byteLength / 1024 / 1024 ).toFixed( 1 )}MB)` );
120
- return cached;
148
+ try {
121
149
 
122
- }
150
+ const buffer = await file.arrayBuffer();
151
+ if ( fromCache ) console.log( `AI Upscaler Worker: model loaded from cache (${( buffer.byteLength / 1024 / 1024 ).toFixed( 1 )}MB)` );
152
+ return buffer;
123
153
 
124
- // Network fetch + cache
125
- const response = await fetch( url );
126
- if ( ! response.ok ) throw new Error( `Failed to fetch model: ${response.status}` );
127
- const buffer = await response.arrayBuffer();
154
+ } finally {
128
155
 
129
- // Cache in background (don't block session creation)
130
- cacheModel( url, buffer.slice( 0 ) );
156
+ release();
131
157
 
132
- return buffer;
158
+ }
133
159
 
134
160
  }
135
161
 
@@ -219,6 +245,7 @@ self.onmessage = async ( e ) => {
219
245
  if ( e.data.ortRuntimeUrl ) _assetConfig.ortRuntimeUrl = e.data.ortRuntimeUrl;
220
246
  if ( e.data.ortWasmPaths ) _assetConfig.ortWasmPaths = e.data.ortWasmPaths;
221
247
  if ( e.data.cacheNamespace ) _assetConfig.cacheNamespace = e.data.cacheNamespace;
248
+ if ( e.data.storage !== undefined ) _assetConfig.storage = e.data.storage;
222
249
  await loadModel( e.data.url, e.data.sessionOptions );
223
250
 
224
251
  } else if ( type === 'infer' ) {
@@ -1,4 +1,5 @@
1
1
  import { BVHBuilder } from '../BVHBuilder.js';
2
+ import { foldLeaves } from '../BVHLeafFold.js';
2
3
  import { createLogger, fmt, applyWorkerLogLevel } from '../../utils/Logger.js';
3
4
 
4
5
  const log = createLogger( 'bvh' );
@@ -133,7 +134,7 @@ function handleAssemble( data ) {
133
134
  const {
134
135
  topFlatData, topNodeCount, frontierMap, subtreeResults,
135
136
  sharedTriangleData, sharedIndices, sharedReorderBuffer,
136
- triangleCount
137
+ triangleCount, foldLeaves: fold
137
138
  } = data;
138
139
 
139
140
  try {
@@ -142,20 +143,22 @@ function handleAssemble( data ) {
142
143
  const builder = new BVHBuilder();
143
144
 
144
145
  // Assemble the final BVH
145
- const bvhData = builder.assembleParallelBVH(
146
- topFlatData, topNodeCount, frontierMap, subtreeResults
147
- );
146
+ const assembled = builder.assembleParallelBVH( topFlatData, topNodeCount, frontierMap, subtreeResults );
147
+ const bvhData = fold ? foldLeaves( assembled ) : assembled;
148
148
 
149
- // Reorder triangles using final indices from SharedArrayBuffer
150
149
  const indices = new Uint32Array( sharedIndices );
151
- const src = new Uint32Array( sharedTriangleData );
152
- const dst = new Uint32Array( sharedReorderBuffer );
150
+ if ( sharedReorderBuffer ) {
153
151
 
154
- for ( let i = 0; i < triangleCount; i ++ ) {
152
+ const src = new Uint32Array( sharedTriangleData );
153
+ const dst = new Uint32Array( sharedReorderBuffer );
154
+
155
+ for ( let i = 0; i < triangleCount; i ++ ) {
156
+
157
+ const srcOff = indices[ i ] * FPT;
158
+ const dstOff = i * FPT;
159
+ dst.set( src.subarray( srcOff, srcOff + FPT ), dstOff );
155
160
 
156
- const srcOff = indices[ i ] * FPT;
157
- const dstOff = i * FPT;
158
- dst.set( src.subarray( srcOff, srcOff + FPT ), dstOff );
161
+ }
159
162
 
160
163
  }
161
164
 
@@ -190,7 +193,7 @@ function handleAssemble( data ) {
190
193
 
191
194
  function handleFullBuild( data ) {
192
195
 
193
- const { triangleData, triangleByteOffset, triangleByteLength, depth, reportProgress, treeletOptimization, reinsertionOptimization, sharedReorderBuffer, maxLeafSize, numBins, maxBins, minBins } = data;
196
+ const { triangleData, triangleByteOffset, triangleByteLength, depth, reportProgress, treeletOptimization, reinsertionOptimization, sharedReorderBuffer, maxLeafSize, numBins, maxBins, minBins, foldLeaves: fold } = data;
194
197
  const builder = new BVHBuilder();
195
198
  // Honor the build params forwarded from SceneProcessor (previously ignored → default leaf 8).
196
199
  if ( maxLeafSize !== undefined ) builder.maxLeafSize = maxLeafSize;
@@ -229,7 +232,8 @@ function handleFullBuild( data ) {
229
232
  const bvhRoot = builder.buildSync( inputTriangles, depth, progressCallback, reorderTarget );
230
233
 
231
234
  const flattenStart = performance.now();
232
- const bvhData = builder.flattenBVH( bvhRoot );
235
+ const flat = builder.flattenBVH( bvhRoot );
236
+ const bvhData = fold ? foldLeaves( flat ) : flat;
233
237
  const flattenTime = performance.now() - flattenStart;
234
238
  log.debug( `flatten ${fmt.ms( flattenTime )} (${fmt.mb( bvhData.byteLength )})` );
235
239