rayzee 5.5.0 → 5.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "5.5.0",
3
+ "version": "5.6.1",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -104,6 +104,7 @@ export class AIUpscaler extends EventDispatcher {
104
104
  this._worker = null;
105
105
  this._currentModelUrl = null;
106
106
  this._tileId = 0;
107
+ this._pendingWorkerHandlers = new Set();
107
108
 
108
109
  // Alpha channel cache (bilinear-upscaled from source, applied per tile)
109
110
  this._upscaledAlpha = null;
@@ -315,6 +316,7 @@ export class AIUpscaler extends EventDispatcher {
315
316
 
316
317
  this._capturedSource = null;
317
318
  this._upscaledAlpha = null;
319
+ this._backupCanvas = null;
318
320
 
319
321
  // Only clean up if abort() hasn't already done it
320
322
  if ( this.state.isUpscaling ) {
@@ -334,6 +336,7 @@ export class AIUpscaler extends EventDispatcher {
334
336
  if ( ! this.state.isUpscaling ) return;
335
337
 
336
338
  this.state.abortController?.abort();
339
+ this._cleanupPendingWorkerHandlers();
337
340
 
338
341
  // Restore input visibility and canvas state
339
342
  this.input.style.opacity = '1';
@@ -610,6 +613,7 @@ export class AIUpscaler extends EventDispatcher {
610
613
 
611
614
  if ( e.data.id !== id ) return;
612
615
  this._worker.removeEventListener( 'message', handler );
616
+ this._pendingWorkerHandlers.delete( handler );
613
617
 
614
618
  if ( e.data.type === 'inferred' ) {
615
619
 
@@ -623,6 +627,7 @@ export class AIUpscaler extends EventDispatcher {
623
627
 
624
628
  };
625
629
 
630
+ this._pendingWorkerHandlers.add( handler );
626
631
  this._worker.addEventListener( 'message', handler );
627
632
  this._worker.postMessage(
628
633
  { type: 'infer', tileData, width, height, id },
@@ -858,9 +863,26 @@ export class AIUpscaler extends EventDispatcher {
858
863
 
859
864
  // ─── Disposal ─────────────────────────────────────────────────────────────
860
865
 
866
+ _cleanupPendingWorkerHandlers() {
867
+
868
+ if ( this._worker ) {
869
+
870
+ for ( const handler of this._pendingWorkerHandlers ) {
871
+
872
+ this._worker.removeEventListener( 'message', handler );
873
+
874
+ }
875
+
876
+ }
877
+
878
+ this._pendingWorkerHandlers.clear();
879
+
880
+ }
881
+
861
882
  async dispose() {
862
883
 
863
884
  this.abort();
885
+ this._cleanupPendingWorkerHandlers();
864
886
 
865
887
  if ( this._worker ) {
866
888
 
@@ -87,6 +87,7 @@ export class OIDNDenoiser extends EventDispatcher {
87
87
  // renderer.getArrayBufferAsync because the source is a raw GPUBuffer,
88
88
  // not a Three.js BufferAttribute.
89
89
  this._alphaReadbackBuffer = null;
90
+ this._alphaReadbackMapped = false;
90
91
 
91
92
  // Cached alpha channel from the input color buffer (OIDN discards alpha)
92
93
  this._cachedAlpha = null;
@@ -108,6 +109,9 @@ export class OIDNDenoiser extends EventDispatcher {
108
109
  abortController: null
109
110
  };
110
111
 
112
+ // Track in-flight tile staging buffers so they can be destroyed on abort
113
+ this._pendingStagingBuffers = new Set();
114
+
111
115
  this.currentTZAUrl = null;
112
116
  this.unet = null;
113
117
 
@@ -514,7 +518,22 @@ export class OIDNDenoiser extends EventDispatcher {
514
518
  this._gpuInputBuffers.albedo?.destroy();
515
519
  this._gpuInputBuffers.normal?.destroy();
516
520
  this._gpuInputPadBuffer?.destroy();
521
+
522
+ // Unmap before destroying if a mapAsync resolved but unmap hasn't been called yet.
523
+ // If mapAsync is still pending, destroy() will reject it — _cacheInputAlpha's
524
+ // catch handler covers that case.
525
+ if ( this._alphaReadbackMapped && this._alphaReadbackBuffer ) {
526
+
527
+ try {
528
+
529
+ this._alphaReadbackBuffer.unmap();
530
+
531
+ } catch { /* already unmapped or destroyed */ }
532
+
533
+ }
534
+
517
535
  this._alphaReadbackBuffer?.destroy();
536
+ this._alphaReadbackMapped = false;
518
537
  this._gpuInputBuffers = { color: null, albedo: null, normal: null };
519
538
  this._gpuInputPadBuffer = null;
520
539
  this._gpuInputPaddedRowBytes = 0;
@@ -550,7 +569,19 @@ export class OIDNDenoiser extends EventDispatcher {
550
569
  enc.copyBufferToBuffer( this._gpuInputBuffers.color, 0, staging, 0, byteSize );
551
570
  device.queue.submit( [ enc.finish() ] );
552
571
 
553
- await staging.mapAsync( GPUMapMode.READ );
572
+ this._alphaReadbackMapped = true;
573
+ try {
574
+
575
+ await staging.mapAsync( GPUMapMode.READ );
576
+
577
+ } catch {
578
+
579
+ // Buffer was destroyed while mapAsync was pending (resize or dispose)
580
+ this._alphaReadbackMapped = false;
581
+ return;
582
+
583
+ }
584
+
554
585
  const f32 = new Float32Array( staging.getMappedRange() );
555
586
 
556
587
  // Extract alpha channel as uint8 (pre-multiplied is not needed — alpha is 0 or 1)
@@ -563,6 +594,7 @@ export class OIDNDenoiser extends EventDispatcher {
563
594
  }
564
595
 
565
596
  staging.unmap();
597
+ this._alphaReadbackMapped = false;
566
598
 
567
599
  this._cachedAlpha = alpha;
568
600
  this._cachedAlphaWidth = width;
@@ -653,6 +685,8 @@ export class OIDNDenoiser extends EventDispatcher {
653
685
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
654
686
  } );
655
687
 
688
+ this._pendingStagingBuffers.add( staging );
689
+
656
690
  // Copy each tile row from its position in the full output buffer
657
691
  const enc = device.createCommandEncoder();
658
692
 
@@ -710,8 +744,15 @@ export class OIDNDenoiser extends EventDispatcher {
710
744
 
711
745
  staging.unmap();
712
746
  staging.destroy();
747
+ this._pendingStagingBuffers.delete( staging );
713
748
  this.ctx.putImageData( tileImageData, tile.x, tile.y );
714
749
 
750
+ } ).catch( () => {
751
+
752
+ // mapAsync rejected (abort or GPU lost) — destroy the buffer
753
+ staging.destroy();
754
+ this._pendingStagingBuffers.delete( staging );
755
+
715
756
  } );
716
757
 
717
758
  }
@@ -745,44 +786,50 @@ export class OIDNDenoiser extends EventDispatcher {
745
786
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
746
787
  } );
747
788
 
748
- // Queue a copy from the oidn output buffer (STORAGE|COPY_SRC) to staging
749
- const encoder = device.createCommandEncoder( { label: 'oidn-readback' } );
750
- encoder.copyBufferToBuffer( gpuBuffer, 0, stagingBuffer, 0, byteSize );
751
- device.queue.submit( [ encoder.finish() ] );
789
+ try {
790
+
791
+ // Queue a copy from the oidn output buffer (STORAGE|COPY_SRC) to staging
792
+ const encoder = device.createCommandEncoder( { label: 'oidn-readback' } );
793
+ encoder.copyBufferToBuffer( gpuBuffer, 0, stagingBuffer, 0, byteSize );
794
+ device.queue.submit( [ encoder.finish() ] );
795
+
796
+ await stagingBuffer.mapAsync( GPUMapMode.READ );
797
+ const float32 = new Float32Array( stagingBuffer.getMappedRange() );
798
+
799
+ const imageData = new ImageData( width, height );
800
+ const exposure = this.getExposure();
801
+ const saturation = this.getSaturation();
802
+ const tmFn = TONE_MAP_FNS.get( this.getToneMapping() ) || TONE_MAP_FNS.get( ACESFilmicToneMapping );
803
+ const alpha = this._cachedAlpha;
752
804
 
753
- await stagingBuffer.mapAsync( GPUMapMode.READ );
754
- const float32 = new Float32Array( stagingBuffer.getMappedRange() );
805
+ for ( let i = 0, len = float32.length; i < len; i += 4 ) {
755
806
 
756
- const imageData = new ImageData( width, height );
757
- const exposure = this.getExposure();
758
- const saturation = this.getSaturation();
759
- const tmFn = TONE_MAP_FNS.get( this.getToneMapping() ) || TONE_MAP_FNS.get( ACESFilmicToneMapping );
760
- const alpha = this._cachedAlpha;
807
+ // Exposure + saturation (pre-tonemap, matching Display)
808
+ let er = float32[ i ] * exposure, eg = float32[ i + 1 ] * exposure, eb = float32[ i + 2 ] * exposure;
809
+ if ( saturation !== 1.0 ) {
761
810
 
762
- for ( let i = 0, len = float32.length; i < len; i += 4 ) {
811
+ _tmOut[ 0 ] = er; _tmOut[ 1 ] = eg; _tmOut[ 2 ] = eb;
812
+ applySaturation( _tmOut, saturation );
813
+ er = _tmOut[ 0 ]; eg = _tmOut[ 1 ]; eb = _tmOut[ 2 ];
763
814
 
764
- // Exposure + saturation (pre-tonemap, matching Display)
765
- let er = float32[ i ] * exposure, eg = float32[ i + 1 ] * exposure, eb = float32[ i + 2 ] * exposure;
766
- if ( saturation !== 1.0 ) {
815
+ }
767
816
 
768
- _tmOut[ 0 ] = er; _tmOut[ 1 ] = eg; _tmOut[ 2 ] = eb;
769
- applySaturation( _tmOut, saturation );
770
- er = _tmOut[ 0 ]; eg = _tmOut[ 1 ]; eb = _tmOut[ 2 ];
817
+ tmFn( er, eg, eb, 1.0, _tmOut );
818
+ imageData.data[ i ] = _tmOut[ 0 ] ** SRGB_GAMMA * 255 | 0;
819
+ imageData.data[ i + 1 ] = _tmOut[ 1 ] ** SRGB_GAMMA * 255 | 0;
820
+ imageData.data[ i + 2 ] = _tmOut[ 2 ] ** SRGB_GAMMA * 255 | 0;
821
+ imageData.data[ i + 3 ] = alpha ? alpha[ i >> 2 ] : 255;
771
822
 
772
823
  }
773
824
 
774
- tmFn( er, eg, eb, 1.0, _tmOut );
775
- imageData.data[ i ] = _tmOut[ 0 ] ** SRGB_GAMMA * 255 | 0;
776
- imageData.data[ i + 1 ] = _tmOut[ 1 ] ** SRGB_GAMMA * 255 | 0;
777
- imageData.data[ i + 2 ] = _tmOut[ 2 ] ** SRGB_GAMMA * 255 | 0;
778
- imageData.data[ i + 3 ] = alpha ? alpha[ i >> 2 ] : 255;
825
+ stagingBuffer.unmap();
826
+ this.ctx.putImageData( imageData, 0, 0 );
779
827
 
780
- }
828
+ } finally {
781
829
 
782
- stagingBuffer.unmap();
783
- stagingBuffer.destroy();
830
+ stagingBuffer.destroy();
784
831
 
785
- this.ctx.putImageData( imageData, 0, 0 );
832
+ }
786
833
 
787
834
  }
788
835
 
@@ -793,6 +840,9 @@ export class OIDNDenoiser extends EventDispatcher {
793
840
  // Signal abort to current operation
794
841
  this.state.abortController?.abort();
795
842
 
843
+ // Destroy any in-flight tile staging buffers that mapAsync won't resolve
844
+ this._destroyPendingStagingBuffers();
845
+
796
846
  // Restore input visibility
797
847
  this.input.style.opacity = '1';
798
848
 
@@ -896,11 +946,26 @@ export class OIDNDenoiser extends EventDispatcher {
896
946
 
897
947
  }
898
948
 
949
+ _destroyPendingStagingBuffers() {
950
+
951
+ for ( const buf of this._pendingStagingBuffers ) {
952
+
953
+ buf.destroy();
954
+
955
+ }
956
+
957
+ this._pendingStagingBuffers.clear();
958
+
959
+ }
960
+
899
961
  dispose() {
900
962
 
901
963
  // Abort any ongoing operations
902
964
  this.abort();
903
965
 
966
+ // Destroy any remaining staging buffers
967
+ this._destroyPendingStagingBuffers();
968
+
904
969
  // Dispose resources
905
970
  this.unet?.dispose();
906
971
  this._destroyGPUInputBuffers();
@@ -120,6 +120,9 @@ export class RenderPipeline {
120
120
 
121
121
  }
122
122
 
123
+ // Prune stale stats entry for removed stage
124
+ this.stats.timings.delete( stage.name );
125
+
123
126
  return true;
124
127
 
125
128
  }
@@ -832,6 +832,24 @@ export class ASVGF extends RenderStage {
832
832
  this._heatmapComputeNode?.dispose();
833
833
  this._heatmapStorageTex?.dispose();
834
834
  this.heatmapTarget?.dispose();
835
+
836
+ // Dispose input TextureNode objects
837
+ this._colorTexNode?.dispose();
838
+ this._normalDepthTexNode?.dispose();
839
+ this._motionTexNode?.dispose();
840
+ this._readTemporalTexNode?.dispose();
841
+ this._readPrevNDTexNode?.dispose();
842
+ this._gradientReadTexNode?.dispose();
843
+
844
+ // Dispose heatmap TextureNode objects
845
+ this._heatmapRawColorTexNode?.dispose();
846
+ this._heatmapColorTexNode?.dispose();
847
+ this._heatmapTemporalTexNode?.dispose();
848
+ this._heatmapNDTexNode?.dispose();
849
+ this._heatmapMotionTexNode?.dispose();
850
+ this._heatmapGradientTexNode?.dispose();
851
+
852
+ // dispose() also removes the DOM node
835
853
  this.heatmapHelper?.dispose();
836
854
 
837
855
  }
@@ -483,6 +483,7 @@ export class AdaptiveSampling extends RenderStage {
483
483
  this._heatmapStorageTex?.dispose();
484
484
  this._outputStorageTex?.dispose();
485
485
  this.heatmapTarget?.dispose();
486
+ this._varianceTexNode?.dispose();
486
487
  this.helper?.dispose();
487
488
 
488
489
  this._computeNode = null;
@@ -490,6 +491,7 @@ export class AdaptiveSampling extends RenderStage {
490
491
  this._heatmapStorageTex = null;
491
492
  this._outputStorageTex = null;
492
493
  this.heatmapTarget = null;
494
+ this._varianceTexNode = null;
493
495
  this.helper = null;
494
496
 
495
497
  }