rayzee 9.3.3 → 9.4.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.
@@ -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
- let cacheBytes = entries.reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
820
- const target = freeBytes ?? Math.max( 0, cacheBytes - budget );
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
- cacheBytes -= meta.bytes ?? 0;
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
- const cacheBytes = ( await this._cacheEntries() ).reduce( ( n, e ) => n + ( e.meta.bytes ?? 0 ), 0 );
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
 
@@ -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
- // Light buffer nodes - pre-allocate for up to 16 lights per type (shader hard cap)
270
- this._lightBuffers = {
271
- directional: uniformArray( new Float32Array( 12 * 16 ), 'float' ),
272
- area: uniformArray( new Float32Array( 16 * 16 ), 'float' ),
273
- point: uniformArray( new Float32Array( 9 * 16 ), 'float' ),
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' );