rayzee 7.23.0 → 7.23.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": "7.23.0",
3
+ "version": "7.23.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",
@@ -63,6 +63,21 @@ const _appsByCanvas = new WeakMap();
63
63
 
64
64
  const log = createLogger( 'engine' );
65
65
 
66
+ /**
67
+ * Attaches the device-lost handler at module scope so the reaction closure captures only
68
+ * `holder` — an arrow written inside a method would share that method's context and pin
69
+ * the app through `this`. See _initRenderer().
70
+ */
71
+ function attachDeviceLostHandler( device, holder ) {
72
+
73
+ device.lost.then( ( info ) => {
74
+
75
+ if ( holder.app ) holder.app._handleDeviceLost( info );
76
+
77
+ } );
78
+
79
+ }
80
+
66
81
  export class PathTracerApp extends EventDispatcher {
67
82
 
68
83
  /**
@@ -169,6 +184,8 @@ export class PathTracerApp extends EventDispatcher {
169
184
  this._trackedListeners = [];
170
185
  this._disposed = false;
171
186
  this._deviceLost = false;
187
+ this._gpuDevice = null;
188
+ this._deviceLostHolder = null;
172
189
 
173
190
  // Deterministic-render mode — see setDeterministicMode()
174
191
  this._deterministic = false;
@@ -539,6 +556,24 @@ export class PathTracerApp extends EventDispatcher {
539
556
 
540
557
  }
541
558
 
559
+ // Value of a WeakMap keyed by a canvas that outlives us: without this the whole
560
+ // disposed graph stays reachable until another app claims the same canvas.
561
+ if ( this.canvas && _appsByCanvas.get( this.canvas ) === this ) _appsByCanvas.delete( this.canvas );
562
+
563
+ if ( this._gpuDevice ) {
564
+
565
+ this._gpuDevice.onuncapturederror = null;
566
+ this._gpuDevice = null;
567
+
568
+ }
569
+
570
+ if ( this._deviceLostHolder ) {
571
+
572
+ this._deviceLostHolder.app = null;
573
+ this._deviceLostHolder = null;
574
+
575
+ }
576
+
542
577
  disposeRenderer( this.renderer );
543
578
  this.renderer = null;
544
579
 
@@ -2319,15 +2354,21 @@ export class PathTracerApp extends EventDispatcher {
2319
2354
  // Detect GPU device loss (dGPU/iGPU switch, driver reset, TDR watchdog on heavy
2320
2355
  // compute). Without this the rAF loop keeps calling render() on a dead device,
2321
2356
  // spewing errors forever. reason 'destroyed' during dispose() is intentional teardown.
2357
+ //
2358
+ // Neither handler may outlive dispose() still holding this app. The device itself does
2359
+ // outlive it: three's RenderObjects.dispose() drops its chain maps without disposing
2360
+ // the render objects, and its Textures manager leaves a listener on every module-level
2361
+ // texture singleton, so backend and device stay reachable for the page's lifetime. A
2362
+ // handler capturing `this` therefore pinned the whole app graph — ~120 MiB of triangle,
2363
+ // BVH and CPU-side texture data per create/dispose cycle. `lost` is a pending promise
2364
+ // whose reaction cannot be unregistered, so it reads the app out of a holder that
2365
+ // dispose() empties; `onuncapturederror` is cleared there directly.
2322
2366
  const gpuDevice = this.renderer.backend?.device;
2323
2367
  if ( gpuDevice?.lost ) {
2324
2368
 
2325
- gpuDevice.lost.then( ( info ) => {
2326
-
2327
- if ( this._disposed ) return;
2328
- this._handleDeviceLost( info );
2329
-
2330
- } );
2369
+ this._gpuDevice = gpuDevice;
2370
+ this._deviceLostHolder = { app: this };
2371
+ attachDeviceLostHandler( gpuDevice, this._deviceLostHolder );
2331
2372
  gpuDevice.onuncapturederror = ( event ) => log.error( 'WebGPU uncaptured error:', event.error );
2332
2373
 
2333
2374
  }
@@ -1,6 +1,10 @@
1
1
  import { SphereGeometry, Mesh, MeshPhysicalMaterial, Group, Object3D, Color, Vector3, ShaderMaterial, Vector2, Matrix4, GLSL3 } from 'three';
2
+ import { QuadMesh } from 'three/webgpu';
2
3
  import { EngineEvents } from '../EngineEvents.js';
3
4
 
5
+ /** Every QuadMesh in the realm shares this one geometry (module-level in three). */
6
+ const _sharedQuadGeometry = /*@__PURE__*/ new QuadMesh( null ).geometry;
7
+
4
8
  let _statusCallback = null;
5
9
 
6
10
  /**
@@ -130,6 +134,18 @@ export function disposeRenderer( renderer ) {
130
134
  renderer.dispose();
131
135
  renderer._canvasTarget = null;
132
136
 
137
+ // Three.js 0.185: `RenderObjects.dispose()` drops its chain maps without disposing the
138
+ // render objects, so each one leaves behind the 'dispose' listener it registered on its
139
+ // geometry. Full-screen passes render a QuadMesh, and every QuadMesh in the realm shares
140
+ // one module-level geometry — so that listener list grows by one entry per renderer and
141
+ // keeps RenderObject → RenderObjects → Bindings → Backend → GPUDevice reachable for the
142
+ // page's lifetime. The chain maps are WeakMaps, so this list is the only handle on them.
143
+ //
144
+ // Clearing it also drops entries belonging to live renderers, which is safe: the
145
+ // singleton geometry is never disposed, so none of these listeners can ever fire.
146
+ const quadDisposeListeners = _sharedQuadGeometry._listeners?.dispose;
147
+ if ( quadDisposeListeners ) quadDisposeListeners.length = 0;
148
+
133
149
  }
134
150
 
135
151
  export function disposeObjectFromMemory( object, exeptions = [] ) {