rayzee 9.3.3 → 9.4.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/README.md +8 -3
- package/dist/rayzee.es.js +3365 -3226
- package/dist/rayzee.umd.js +93 -93
- package/package.json +1 -1
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +25 -13
- package/src/Processor/TextureCreator.js +170 -6
- package/src/Processor/Workers/TexturesWorker.js +98 -12
- package/src/Stages/PathTracer.js +13 -2
- package/src/Stages/PathTracerStage.js +27 -42
- package/src/Storage/StorageManager.js +43 -5
- package/src/managers/CameraManager.js +55 -34
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/UniformManager.js +30 -7
|
@@ -17,6 +17,8 @@ const MAX_DEFAULT_BUDGET = 100 * 1024 ** 3;
|
|
|
17
17
|
const FREE_MARGIN = 64 * 1024 ** 2;
|
|
18
18
|
const STREAM_BATCH = 8 * 1024 ** 2;
|
|
19
19
|
const STREAM_WINDOW = 4;
|
|
20
|
+
// Other tabs write too, so the cache total kept in memory is listed again at most this often.
|
|
21
|
+
const CACHE_TOTAL_TTL_MS = 30_000;
|
|
20
22
|
|
|
21
23
|
const encoder = new TextEncoder();
|
|
22
24
|
|
|
@@ -152,6 +154,7 @@ export class EntryWriter {
|
|
|
152
154
|
this._sizes = { ...sizes };
|
|
153
155
|
this._append = append;
|
|
154
156
|
this._done = false;
|
|
157
|
+
this._committedBytes = append ? Object.values( sizes ).reduce( ( n, b ) => n + b, 0 ) : 0;
|
|
155
158
|
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -313,6 +316,7 @@ export class EntryWriter {
|
|
|
313
316
|
|
|
314
317
|
const { args, list } = transferable( JSON.stringify( meta ), { copy: false } );
|
|
315
318
|
await this._transport.call( 'writeFile', { path: this._path, name: META, ...args }, list );
|
|
319
|
+
if ( this.area.kind === STORAGE_KIND.CACHE ) this.area.manager._countCacheBytes( meta.bytes - this._committedBytes );
|
|
316
320
|
this.area.manager._changed( this.area.name );
|
|
317
321
|
return meta;
|
|
318
322
|
|
|
@@ -700,6 +704,7 @@ export class StorageManager extends Emitter {
|
|
|
700
704
|
this._transport = transport;
|
|
701
705
|
this._areas = new Map();
|
|
702
706
|
this._budget = null;
|
|
707
|
+
this._cacheTotal = null;
|
|
703
708
|
|
|
704
709
|
for ( const name of Object.values( ENGINE_AREAS ) ) this.defineArea( name, { kind: name === ENGINE_AREAS.SPILL ? STORAGE_KIND.SCRATCH : STORAGE_KIND.CACHE } );
|
|
705
710
|
|
|
@@ -775,6 +780,40 @@ export class StorageManager extends Emitter {
|
|
|
775
780
|
|
|
776
781
|
}
|
|
777
782
|
|
|
783
|
+
/**
|
|
784
|
+
* What cache areas hold together. Every new download asks, and a listing reads every entry's
|
|
785
|
+
* metadata, so it is listed once and kept current by this manager's own commits.
|
|
786
|
+
*/
|
|
787
|
+
async _cacheBytes() {
|
|
788
|
+
|
|
789
|
+
let total = this._cacheTotal;
|
|
790
|
+
if ( ! total || Date.now() - total.at > CACHE_TOTAL_TTL_MS ) {
|
|
791
|
+
|
|
792
|
+
total = this._cacheTotal = { at: Date.now(), bytes: 0, ready: null };
|
|
793
|
+
total.ready = this._cacheEntries().then( ( entries ) => {
|
|
794
|
+
|
|
795
|
+
total.bytes += entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
|
|
796
|
+
|
|
797
|
+
} );
|
|
798
|
+
total.ready.catch( () => {
|
|
799
|
+
|
|
800
|
+
if ( this._cacheTotal === total ) this._cacheTotal = null;
|
|
801
|
+
|
|
802
|
+
} );
|
|
803
|
+
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
await total.ready;
|
|
807
|
+
return total.bytes;
|
|
808
|
+
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
_countCacheBytes( bytes ) {
|
|
812
|
+
|
|
813
|
+
if ( this._cacheTotal ) this._cacheTotal.bytes += bytes;
|
|
814
|
+
|
|
815
|
+
}
|
|
816
|
+
|
|
778
817
|
async _cacheEntries() {
|
|
779
818
|
|
|
780
819
|
const out = [];
|
|
@@ -816,8 +855,8 @@ export class StorageManager extends Emitter {
|
|
|
816
855
|
|
|
817
856
|
const entries = await this._cacheEntries();
|
|
818
857
|
const budget = await this.budget();
|
|
819
|
-
|
|
820
|
-
const target = freeBytes ?? Math.max( 0,
|
|
858
|
+
const total = this._cacheTotal = { at: Date.now(), bytes: entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 ), ready: null };
|
|
859
|
+
const target = freeBytes ?? Math.max( 0, total.bytes - budget );
|
|
821
860
|
if ( target <= 0 ) return 0;
|
|
822
861
|
|
|
823
862
|
const held = await heldLockNames();
|
|
@@ -832,7 +871,7 @@ export class StorageManager extends Emitter {
|
|
|
832
871
|
if ( await area.removeById( meta.id ) ) {
|
|
833
872
|
|
|
834
873
|
freed += meta.bytes ?? 0;
|
|
835
|
-
|
|
874
|
+
total.bytes -= meta.bytes ?? 0;
|
|
836
875
|
|
|
837
876
|
}
|
|
838
877
|
|
|
@@ -855,8 +894,7 @@ export class StorageManager extends Emitter {
|
|
|
855
894
|
const free = bytes + FREE_MARGIN - ( quota - usage );
|
|
856
895
|
if ( kind !== STORAGE_KIND.CACHE ) return { free, budget: 0 };
|
|
857
896
|
|
|
858
|
-
|
|
859
|
-
return { free, budget: cacheBytes + bytes - await this.budget() };
|
|
897
|
+
return { free, budget: await this._cacheBytes() + bytes - await this.budget() };
|
|
860
898
|
|
|
861
899
|
};
|
|
862
900
|
|
|
@@ -312,40 +312,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
312
312
|
|
|
313
313
|
} else {
|
|
314
314
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this.camera.position.copy( sourceCamera.position );
|
|
318
|
-
this.camera.quaternion.copy( sourceCamera.quaternion );
|
|
319
|
-
// An imported camera can carry a mirror (a negative axis scale) that no
|
|
320
|
-
// quaternion can express — a pbrt scene's `Scale -1 1 1`, for one. Copying
|
|
321
|
-
// pose alone would silently un-mirror the view.
|
|
322
|
-
this.camera.scale.copy( sourceCamera.scale );
|
|
323
|
-
if ( sourceCamera.isPerspectiveCamera ) this.camera.fov = sourceCamera.fov;
|
|
324
|
-
this.camera.near = sourceCamera.near;
|
|
325
|
-
this.camera.far = sourceCamera.far;
|
|
326
|
-
this.camera.updateProjectionMatrix();
|
|
327
|
-
this.camera.updateMatrixWorld( true );
|
|
328
|
-
|
|
329
|
-
// Restore the saved orbit target for user cameras; otherwise place it
|
|
330
|
-
// along the forward direction at the focus distance.
|
|
331
|
-
if ( this.controls ) {
|
|
332
|
-
|
|
333
|
-
const savedTarget = sourceCamera.userData?.__rayzeeOrbitTarget;
|
|
334
|
-
if ( savedTarget ) {
|
|
335
|
-
|
|
336
|
-
this.controls.target.copy( savedTarget );
|
|
337
|
-
|
|
338
|
-
} else {
|
|
339
|
-
|
|
340
|
-
const forward = new Vector3( 0, 0, - 1 ).applyQuaternion( sourceCamera.quaternion );
|
|
341
|
-
const focusDist = focusDistance || 5.0;
|
|
342
|
-
this.controls.target.copy( this.camera.position ).addScaledVector( forward, focusDist );
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
this.controls.update();
|
|
347
|
-
|
|
348
|
-
}
|
|
315
|
+
this._placeAt( this.cameras[ index ], focusDistance );
|
|
349
316
|
|
|
350
317
|
}
|
|
351
318
|
|
|
@@ -370,6 +337,60 @@ export class CameraManager extends EventDispatcher {
|
|
|
370
337
|
|
|
371
338
|
}
|
|
372
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Puts the active camera back at the view it starts from: the default camera at the framing the
|
|
342
|
+
* model loaded with, any other at its own pose and lens. Its effects and projection stay.
|
|
343
|
+
*/
|
|
344
|
+
resetView() {
|
|
345
|
+
|
|
346
|
+
const source = this.currentCameraIndex > 0 ? this.cameras[ this.currentCameraIndex ] : null;
|
|
347
|
+
if ( ! source ) {
|
|
348
|
+
|
|
349
|
+
this.controls.reset();
|
|
350
|
+
return;
|
|
351
|
+
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this._placeAt( source, this._getSettings?.( 'focusDistance' ) );
|
|
355
|
+
const height = orthoHeightOf( source );
|
|
356
|
+
if ( this.camera.orthographic ) this._setView( true, height ? height / 2 : this.camera.orthoHalfHeight );
|
|
357
|
+
this._onReset?.();
|
|
358
|
+
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// The view takes a camera's pose and lens, orbiting its saved target or a point ahead at the focus distance.
|
|
362
|
+
_placeAt( source, focusDistance ) {
|
|
363
|
+
|
|
364
|
+
this.camera.position.copy( source.position );
|
|
365
|
+
this.camera.quaternion.copy( source.quaternion );
|
|
366
|
+
// An imported camera can carry a mirror (a negative axis scale) that no
|
|
367
|
+
// quaternion can express — a pbrt scene's `Scale -1 1 1`, for one. Copying
|
|
368
|
+
// pose alone would silently un-mirror the view.
|
|
369
|
+
this.camera.scale.copy( source.scale );
|
|
370
|
+
if ( source.isPerspectiveCamera ) this.camera.fov = source.fov;
|
|
371
|
+
this.camera.near = source.near;
|
|
372
|
+
this.camera.far = source.far;
|
|
373
|
+
this.camera.updateProjectionMatrix();
|
|
374
|
+
this.camera.updateMatrixWorld( true );
|
|
375
|
+
|
|
376
|
+
if ( ! this.controls ) return;
|
|
377
|
+
|
|
378
|
+
const savedTarget = source.userData?.__rayzeeOrbitTarget;
|
|
379
|
+
if ( savedTarget ) {
|
|
380
|
+
|
|
381
|
+
this.controls.target.copy( savedTarget );
|
|
382
|
+
|
|
383
|
+
} else {
|
|
384
|
+
|
|
385
|
+
const forward = new Vector3( 0, 0, - 1 ).applyQuaternion( source.quaternion );
|
|
386
|
+
this.controls.target.copy( this.camera.position ).addScaledVector( forward, focusDistance || 5.0 );
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this.controls.update();
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
373
394
|
/**
|
|
374
395
|
* Makes the camera orthographic at `height`, or perspective for null, then hands the `cameraProjection`
|
|
375
396
|
* setting the outcome — after, so its handler finds the camera already there. A 360° panorama stays on
|
|
@@ -71,6 +71,7 @@ export class GoboManager {
|
|
|
71
71
|
cnv.width = width;
|
|
72
72
|
cnv.height = height;
|
|
73
73
|
const ctx = cnv.getContext( '2d', { willReadFrequently: true } );
|
|
74
|
+
let lastYield = performance.now();
|
|
74
75
|
|
|
75
76
|
for ( let i = 0; i < depth; i ++ ) {
|
|
76
77
|
|
|
@@ -79,6 +80,14 @@ export class GoboManager {
|
|
|
79
80
|
const img = ctx.getImageData( 0, 0, width, height );
|
|
80
81
|
data.set( img.data, i * width * height * 4 );
|
|
81
82
|
|
|
83
|
+
// Each first draw decodes its image, so the library packs in short tasks.
|
|
84
|
+
if ( performance.now() - lastYield > 16 ) {
|
|
85
|
+
|
|
86
|
+
await new Promise( resolve => setTimeout( resolve, 0 ) );
|
|
87
|
+
lastYield = performance.now();
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
const tex = new DataArrayTexture( data, width, height, depth );
|
|
@@ -19,6 +19,31 @@ const SHADER_NAMES = {
|
|
|
19
19
|
cameraProjectionMatrix: 'ptCameraProjectionMatrix',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/** Floats per light in each type's list (see LightSerializer). */
|
|
23
|
+
export const LIGHT_FLOATS = Object.freeze( {
|
|
24
|
+
directional: 12, // 8 light fields + gobo { index, signed intensity, scale, pad }
|
|
25
|
+
area: 16, // 13 base + normalize, spread, shape
|
|
26
|
+
point: 9,
|
|
27
|
+
spot: 20, // 14 light fields + gobo { index, signed intensity } + IES { index, intensity } + 2 reserved
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
/** Lights a list holds before it has to grow. */
|
|
31
|
+
export const LIGHT_LIST_STEP = 16;
|
|
32
|
+
|
|
33
|
+
// A uniform array gives each float its own vec4; WebGPU guarantees 64 KiB a binding.
|
|
34
|
+
const MAX_UNIFORM_ELEMENTS = 65536 / 16;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Lights a list of this type must hold for `lights`: a whole number of steps, but never past what
|
|
38
|
+
* fits a binding unless `lights` alone already does — a count that did not fit never did.
|
|
39
|
+
*/
|
|
40
|
+
export function lightListCapacity( type, lights ) {
|
|
41
|
+
|
|
42
|
+
const stepped = Math.ceil( lights / LIGHT_LIST_STEP ) * LIGHT_LIST_STEP;
|
|
43
|
+
return Math.max( lights, Math.min( stepped, Math.floor( MAX_UNIFORM_ELEMENTS / LIGHT_FLOATS[ type ] ) ) );
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
export class UniformManager {
|
|
23
48
|
|
|
24
49
|
constructor( width = 1920, height = 1080 ) {
|
|
@@ -266,13 +291,11 @@ export class UniformManager {
|
|
|
266
291
|
u( 'numPointLights', 0, 'int' );
|
|
267
292
|
u( 'numSpotLights', 0, 'int' );
|
|
268
293
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
spot: uniformArray( new Float32Array( 20 * 16 ), 'float' ),
|
|
275
|
-
};
|
|
294
|
+
// The shader bakes each list's length, so lights are written into these in place and a list is
|
|
295
|
+
// only replaced when a scene has more lights than it holds (PathTracerStage._writeLightList).
|
|
296
|
+
this._lightBuffers = Object.fromEntries( Object.entries( LIGHT_FLOATS ).map(
|
|
297
|
+
( [ type, floats ] ) => [ type, uniformArray( new Float32Array( floats * LIGHT_LIST_STEP ), 'float' ) ]
|
|
298
|
+
) );
|
|
276
299
|
|
|
277
300
|
// Camera matrices
|
|
278
301
|
u( 'cameraWorldMatrix', new Matrix4(), 'mat4' );
|