rayzee 6.5.0 → 7.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 (51) hide show
  1. package/README.md +24 -5
  2. package/dist/rayzee.es.js +7624 -7063
  3. package/dist/rayzee.es.js.map +1 -1
  4. package/dist/rayzee.umd.js +157 -236
  5. package/dist/rayzee.umd.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/EngineDefaults.js +26 -9
  8. package/src/PathTracerApp.js +118 -26
  9. package/src/Pipeline/PipelineContext.js +1 -2
  10. package/src/Pipeline/RenderPipeline.js +1 -1
  11. package/src/Pipeline/RenderStage.js +1 -1
  12. package/src/Processor/CameraOptimizer.js +0 -5
  13. package/src/Processor/GeometryExtractor.js +6 -0
  14. package/src/Processor/KernelManager.js +277 -0
  15. package/src/Processor/PackedRayBuffer.js +291 -0
  16. package/src/Processor/QueueManager.js +173 -0
  17. package/src/Processor/SceneProcessor.js +1 -0
  18. package/src/Processor/ShaderBuilder.js +11 -317
  19. package/src/Processor/StorageTexturePool.js +29 -15
  20. package/src/Processor/VRAMTracker.js +169 -0
  21. package/src/Processor/utils.js +11 -110
  22. package/src/RenderSettings.js +0 -3
  23. package/src/Stages/ASVGF.js +151 -78
  24. package/src/Stages/BilateralFilter.js +34 -10
  25. package/src/Stages/EdgeFilter.js +2 -3
  26. package/src/Stages/MotionVector.js +16 -9
  27. package/src/Stages/NormalDepth.js +17 -5
  28. package/src/Stages/PathTracer.js +671 -1456
  29. package/src/Stages/PathTracerStage.js +1451 -0
  30. package/src/Stages/SSRC.js +32 -15
  31. package/src/Stages/Variance.js +35 -12
  32. package/src/TSL/CompactKernel.js +110 -0
  33. package/src/TSL/DebugKernel.js +98 -0
  34. package/src/TSL/Environment.js +13 -11
  35. package/src/TSL/ExtendKernel.js +75 -0
  36. package/src/TSL/FinalWriteKernel.js +121 -0
  37. package/src/TSL/GenerateKernel.js +111 -0
  38. package/src/TSL/LightsSampling.js +2 -2
  39. package/src/TSL/PathTracerCore.js +43 -1039
  40. package/src/TSL/ShadeKernel.js +876 -0
  41. package/src/TSL/patches.js +81 -4
  42. package/src/index.js +3 -0
  43. package/src/managers/CameraManager.js +1 -1
  44. package/src/managers/DenoisingManager.js +40 -75
  45. package/src/managers/EnvironmentManager.js +30 -39
  46. package/src/managers/OverlayManager.js +7 -22
  47. package/src/managers/UniformManager.js +0 -3
  48. package/src/managers/helpers/TileHelper.js +2 -2
  49. package/src/Stages/AdaptiveSampling.js +0 -483
  50. package/src/TSL/PathTracer.js +0 -384
  51. package/src/managers/TileManager.js +0 -298
@@ -0,0 +1,173 @@
1
+ /**
2
+ * QueueManager.js — wavefront ray queues: active indices (ping-pong), sorted indices, atomic counters.
3
+ */
4
+
5
+ import { storage } from 'three/tsl';
6
+ import { StorageInstancedBufferAttribute } from 'three/webgpu';
7
+
8
+ /** Counter indices — must match ResetCounters kernel */
9
+ export const COUNTER = {
10
+ ACTIVE_RAY_COUNT: 0,
11
+ // rays entering current bounce; snapshotted before ACTIVE_RAY_COUNT reset so over-sized dispatch is safe.
12
+ ENTERING_COUNT: 1,
13
+ COUNT: 2,
14
+ };
15
+
16
+ /** Ray flag bits packed into rayBounceFlags (uint) */
17
+ export const RAY_FLAG = {
18
+ BOUNCE_MASK: 0xFF, // bits 0-7: bounce count (0-255)
19
+ ACTIVE: 1 << 8, // bit 8: ray is alive
20
+ SPECULAR: 1 << 9, // bit 9: last bounce was specular
21
+ INSIDE_MEDIUM: 1 << 10, // bit 10: ray is inside a transmissive medium
22
+ // bits 11-15: ray type
23
+ RAY_TYPE_SHIFT: 11,
24
+ RAY_TYPE_MASK: 0x1F << 11,
25
+ // bits 16-31: spare per-ray state carried across bounces
26
+ HAS_HIT_OPAQUE: 1 << 16, // bit 16: ray chain has hit non-transmissive geometry (transparent-bg alpha; megakernel hasHitOpaqueSurface)
27
+ AUX_LOCKED: 1 << 17, // bit 17: OIDN aux (normal/albedo) locked onto first non-specular hit (megakernel auxLocked)
28
+ };
29
+
30
+ export class QueueManager {
31
+
32
+ /**
33
+ * @param {number} maxRays - Maximum number of rays (typically width * height)
34
+ */
35
+ constructor( maxRays = 0 ) {
36
+
37
+ this.capacity = 0;
38
+ this.counters = null;
39
+ // A/B alternate: one read by current bounce, other written by compaction
40
+ this.activeIndices = null;
41
+ this.activeIndicesRO = null;
42
+ this.pingPong = 0; // 0 = read A / write B, 1 = read B / write A
43
+
44
+ if ( maxRays > 0 ) {
45
+
46
+ this.allocate( maxRays );
47
+
48
+ }
49
+
50
+ }
51
+
52
+ // capacity must match RayBufferPool.allocatedCapacity
53
+ allocate( capacity ) {
54
+
55
+ this.dispose();
56
+ this.capacity = capacity;
57
+
58
+ // explicit attribute (not attributeArray) so it can be referenced for async readback
59
+ this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.COUNT ), 1 );
60
+ this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
61
+
62
+ // per-bounce ACTIVE_RAY_COUNT snapshots; read back async to size/skip late bounces next frame
63
+ this.MAX_BOUNCE_SNAPSHOTS = 32;
64
+ this._bounceCountsAttr = new StorageInstancedBufferAttribute(
65
+ new Uint32Array( this.MAX_BOUNCE_SNAPSHOTS ), 1,
66
+ );
67
+ this.bounceCounts = storage( this._bounceCountsAttr, 'uint' );
68
+
69
+ const attrA = new StorageInstancedBufferAttribute( new Uint32Array( capacity ), 1 );
70
+ const attrB = new StorageInstancedBufferAttribute( new Uint32Array( capacity ), 1 );
71
+ this._attrA = attrA;
72
+ this._attrB = attrB;
73
+
74
+ this.activeIndices = {
75
+ a: storage( attrA, 'uint' ),
76
+ b: storage( attrB, 'uint' ),
77
+ };
78
+
79
+ // RO reuses the same attribute so RW/RO share one GPU buffer
80
+ this.activeIndicesRO = {
81
+ a: storage( attrA, 'uint' ).toReadOnly(),
82
+ b: storage( attrB, 'uint' ).toReadOnly(),
83
+ };
84
+
85
+ this.pingPong = 0;
86
+
87
+ const totalBytes = (
88
+ COUNTER.COUNT * 4 +
89
+ capacity * 4 * 2
90
+ );
91
+
92
+ console.log(
93
+ `QueueManager: Allocated capacity=${capacity}, ` +
94
+ `total=${( totalBytes / ( 1024 * 1024 ) ).toFixed( 1 )} MB`
95
+ );
96
+
97
+ }
98
+
99
+ // returns true if reallocation occurred
100
+ resize( capacity ) {
101
+
102
+ if ( capacity <= this.capacity && this.capacity > 0 ) return false;
103
+ this.allocate( capacity );
104
+ return true;
105
+
106
+ }
107
+
108
+ getCounters() {
109
+
110
+ return this.counters;
111
+
112
+ }
113
+
114
+ getActiveReadRO() {
115
+
116
+ return this.pingPong === 0 ? this.activeIndicesRO.a : this.activeIndicesRO.b;
117
+
118
+ }
119
+
120
+ // RW version for compaction input
121
+ getActiveRead() {
122
+
123
+ return this.pingPong === 0 ? this.activeIndices.a : this.activeIndices.b;
124
+
125
+ }
126
+
127
+ getActiveWrite() {
128
+
129
+ return this.pingPong === 0 ? this.activeIndices.b : this.activeIndices.a;
130
+
131
+ }
132
+
133
+ // raw attribute for `renderer.getArrayBufferAsync(...)` readback
134
+ getCountersAttribute() {
135
+
136
+ return this._countersAttr;
137
+
138
+ }
139
+
140
+ getBounceCounts() {
141
+
142
+ return this.bounceCounts;
143
+
144
+ }
145
+
146
+ getBounceCountsAttribute() {
147
+
148
+ return this._bounceCountsAttr;
149
+
150
+ }
151
+
152
+ swap() {
153
+
154
+ this.pingPong = 1 - this.pingPong;
155
+
156
+ }
157
+
158
+ resetPingPong() {
159
+
160
+ this.pingPong = 0;
161
+
162
+ }
163
+
164
+ dispose() {
165
+
166
+ this.counters = null;
167
+ this.activeIndices = null;
168
+ this.activeIndicesRO = null;
169
+ this.capacity = 0;
170
+
171
+ }
172
+
173
+ }
@@ -297,6 +297,7 @@ export class SceneProcessor {
297
297
  // Store other extracted data
298
298
  this.materials = extractedData.materials;
299
299
  this.materialCount = this.materials.length; // Store material count for feature scanning
300
+ this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
300
301
  this.meshes = extractedData.meshes;
301
302
  this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
302
303
  this.maps = extractedData.maps;
@@ -1,110 +1,29 @@
1
1
  /**
2
- * ShaderBuilder.js
3
- * Owns TSL shader graph construction and compute node management
4
- * for the path tracing pipeline.
2
+ * ShaderBuilder.js — shared scene texture-node factory for the path tracer.
5
3
  *
6
- * "Copy approach": Single compute node writes to 3 write-only StorageTextures.
7
- * Previous-frame reads use texture() sampling from a MRT RenderTarget
8
- * (populated by copyTextureToTexture after each dispatch).
9
- *
10
- * Texture nodes are created once and updated in-place via .value mutation
11
- * to preserve compiled shader graph references.
4
+ * Creates the texture/storage nodes the wavefront kernels read (environment, material map
5
+ * arrays, previous-frame MRT, adaptive-sampling, gobo/IES) and configures the module-level
6
+ * shadow/alpha/gobo/IES shader state. Nodes are created once and updated in-place via
7
+ * .value mutation to preserve compiled shader-graph references.
12
8
  */
13
9
 
14
- import { Fn, texture, vec2, float, int, uniform, If,
15
- localId, workgroupId } from 'three/tsl';
16
- import { TextureNode } from 'three/webgpu';
10
+ import { texture } from 'three/tsl';
17
11
  import { LinearFilter, DataArrayTexture } from 'three';
18
- import { pathTracerMain } from '../TSL/PathTracer.js';
19
12
  import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
20
13
  import { setGoboMapsTexture, setIESProfilesTexture } from '../TSL/LightsCore.js';
21
- import { BuildTimer } from './BuildTimer.js';
22
-
23
- const WG_SIZE = 8;
24
14
 
25
15
  export class ShaderBuilder {
26
16
 
27
17
  constructor() {
28
18
 
29
- // Single compute node (no dual ping-pong — copy approach)
30
- this.computeNode = null;
31
-
32
19
  // Previous-frame texture nodes (sample from MRT RenderTarget)
33
20
  this.prevColorTexNode = null;
34
21
  this.prevNormalDepthTexNode = null;
35
22
  this.prevAlbedoTexNode = null;
36
23
 
37
- // Adaptive sampling texture (updated per-frame from context)
38
- this.adaptiveSamplingTexNode = null;
39
-
40
- // Tile offset uniforms — pixel origin of the active tile region
41
- // Dispatch covers only tile-sized workgroups; offset maps them to image space
42
- this.tileOffsetX = uniform( 0, 'int' );
43
- this.tileOffsetY = uniform( 0, 'int' );
44
-
45
- // Render dimensions for edge-workgroup bounds checking
46
- this.renderWidth = uniform( 1920, 'int' );
47
- this.renderHeight = uniform( 1080, 'int' );
48
-
49
- // Dispatch dimensions
50
- this._dispatchX = 0;
51
- this._dispatchY = 0;
52
-
53
- // Reused per-frame dispatchSize array — avoids GC pressure from
54
- // allocating [x,y,z] on every setFullScreenDispatch/setTileDispatch call.
55
- // WebGPUBackend only reads indices 0..2 of this array during compute dispatch.
56
- this._dispatchSize = [ 0, 0, 1 ];
57
-
58
24
  // Scene texture nodes cache (for in-place updates on model change)
59
25
  this._sceneTextureNodes = null;
60
26
 
61
- // Whether the GPU compute pipeline has been compiled (via a real dispatch).
62
- // Reset on setupCompute() rebuilds and on dispose().
63
- this._compiled = false;
64
-
65
- }
66
-
67
- /**
68
- * Creates the full compute shader graph from scratch.
69
- *
70
- * @param {Object} config
71
- * @param {Object} config.stage - PathTracer instance
72
- * @param {Object} config.storageTextures - StorageTexturePool
73
- */
74
- setupCompute( config ) {
75
-
76
- const { stage, storageTextures } = config;
77
-
78
- const timer = new BuildTimer( 'setupCompute' );
79
-
80
- timer.start( 'Create texture nodes' );
81
- const textureNodes = this._createTextureNodes( stage, storageTextures );
82
- timer.end( 'Create texture nodes' );
83
-
84
- timer.start( 'Build compute node (TSL)' );
85
-
86
- const width = storageTextures.renderWidth;
87
- const height = storageTextures.renderHeight;
88
- this._dispatchX = Math.ceil( width / WG_SIZE );
89
- this._dispatchY = Math.ceil( height / WG_SIZE );
90
-
91
- this.renderWidth.value = width;
92
- this.renderHeight.value = height;
93
-
94
- const writeTex = storageTextures.getWriteTextures();
95
-
96
- this.computeNode = this._buildComputeNode(
97
- stage, textureNodes,
98
- writeTex.color, writeTex.normalDepth, writeTex.albedo
99
- );
100
-
101
- // New compute node → needs a fresh GPU pipeline compile
102
- this._compiled = false;
103
-
104
- timer.end( 'Build compute node (TSL)' );
105
-
106
- timer.print();
107
-
108
27
  }
109
28
 
110
29
  updateSceneTextures( stage ) {
@@ -165,98 +84,10 @@ export class ShaderBuilder {
165
84
 
166
85
  }
167
86
 
168
- setSize( width, height ) {
169
-
170
- this._dispatchX = Math.ceil( width / WG_SIZE );
171
- this._dispatchY = Math.ceil( height / WG_SIZE );
172
-
173
- if ( this.computeNode ) {
174
-
175
- this._dispatchSize[ 0 ] = this._dispatchX;
176
- this._dispatchSize[ 1 ] = this._dispatchY;
177
- this.computeNode.dispatchSize = this._dispatchSize;
178
-
179
- }
180
-
181
- this.renderWidth.value = width;
182
- this.renderHeight.value = height;
183
-
184
- // Reset tile offset (full-screen)
185
- this.tileOffsetX.value = 0;
186
- this.tileOffsetY.value = 0;
187
-
188
- }
189
-
190
- /**
191
- * Set dispatch to cover only the active tile region.
192
- * Adjusts dispatch count and tile offset so threads map directly to tile pixels.
193
- * @param {number} offsetX - Tile origin X in pixels
194
- * @param {number} offsetY - Tile origin Y in pixels
195
- * @param {number} tileWidth - Tile width in pixels
196
- * @param {number} tileHeight - Tile height in pixels
197
- */
198
- setTileDispatch( offsetX, offsetY, tileWidth, tileHeight ) {
199
-
200
- this.tileOffsetX.value = offsetX;
201
- this.tileOffsetY.value = offsetY;
202
-
203
- const dispatchX = Math.ceil( tileWidth / WG_SIZE );
204
- const dispatchY = Math.ceil( tileHeight / WG_SIZE );
205
-
206
- if ( this.computeNode ) {
207
-
208
- this._dispatchSize[ 0 ] = dispatchX;
209
- this._dispatchSize[ 1 ] = dispatchY;
210
- this.computeNode.dispatchSize = this._dispatchSize;
211
-
212
- }
213
-
214
- }
215
-
216
- /**
217
- * Reset dispatch to full-screen (no tiling).
218
- */
219
- setFullScreenDispatch() {
220
-
221
- this.tileOffsetX.value = 0;
222
- this.tileOffsetY.value = 0;
223
-
224
- if ( this.computeNode ) {
225
-
226
- this._dispatchSize[ 0 ] = this._dispatchX;
227
- this._dispatchSize[ 1 ] = this._dispatchY;
228
- this.computeNode.dispatchSize = this._dispatchSize;
229
-
230
- }
231
-
232
- }
233
-
234
- /**
235
- * Front-load GPU compute pipeline creation via a single dispatch.
236
- *
237
- * Three.js WebGPU has no `createComputePipelineAsync` path — compute
238
- * pipelines always compile synchronously on first `renderer.compute(node)`.
239
- * Calling this at build time (while a "Compiling shaders…" status is
240
- * already visible) moves the stall off the first animate frame.
241
- *
242
- * The dispatch writes to ping-pong storage textures whose contents are
243
- * discarded by the subsequent `reset()` (frame counter back to 0 →
244
- * `hasPreviousAccumulated = 0` → prev textures are not read).
245
- *
246
- * @param {object} renderer - WebGPURenderer
247
- */
248
- forceCompile( renderer ) {
249
-
250
- if ( this._compiled || ! this.computeNode || ! renderer ) return;
251
-
252
- this._compiled = true;
253
- renderer.compute( this.computeNode );
254
-
255
- }
256
-
257
- // ===== PRIVATE =====
258
-
259
- _createTextureNodes( stage, storageTextures ) {
87
+ // Creates the shared scene texture nodes (env, material maps, prev-frame, adaptive, gobo, IES)
88
+ // + configures the module-level shadow/alpha/gobo/IES shader state read by the wavefront kernels.
89
+ // Call from setupMaterial before the kernels are built.
90
+ createSceneTextureNodes( stage, storageTextures ) {
260
91
 
261
92
  const triStorage = stage.triangleStorageNode;
262
93
  const bvhStorage = stage.bvhStorageNode;
@@ -270,14 +101,6 @@ export class ShaderBuilder {
270
101
 
271
102
  const envTex = texture( stage.environment.environmentTexture );
272
103
 
273
- // Adaptive sampling texture
274
- const adaptiveSamplingTex = new TextureNode();
275
- this.adaptiveSamplingTexNode = adaptiveSamplingTex;
276
-
277
- // Environment importance sampling CDF — packed storage buffer
278
- // Layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
279
- const envCDFStorage = stage.environment.envCDFStorageNode;
280
-
281
104
  // Previous-frame texture nodes — initialized from readTarget textures
282
105
  const readTextures = storageTextures.getReadTextures();
283
106
  this.prevColorTexNode = texture( readTextures.color );
@@ -319,7 +142,7 @@ export class ShaderBuilder {
319
142
 
320
143
  const result = {
321
144
  triStorage, bvhStorage, matStorage, lightBufferStorage,
322
- envTex, adaptiveSamplingTex, envCDFStorage,
145
+ envTex,
323
146
  albedoMapsTex, normalMapsTex, bumpMapsTex,
324
147
  metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
325
148
  goboMapsTex, iesProfilesTex,
@@ -330,141 +153,12 @@ export class ShaderBuilder {
330
153
 
331
154
  }
332
155
 
333
- /**
334
- * Build a single compute node.
335
- * Previous-frame reads use texture() nodes bound to MRT RenderTarget textures.
336
- */
337
- _buildComputeNode( stage, textureNodes,
338
- writeColorTex, writeNDTex, writeAlbedoTex ) {
339
-
340
- const {
341
- triStorage, bvhStorage, matStorage, lightBufferStorage,
342
- envTex, adaptiveSamplingTex, envCDFStorage,
343
- albedoMapsTex, normalMapsTex, bumpMapsTex,
344
- metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
345
- } = textureNodes;
346
-
347
- const tileOffsetX = this.tileOffsetX;
348
- const tileOffsetY = this.tileOffsetY;
349
- const renderWidth = this.renderWidth;
350
- const renderHeight = this.renderHeight;
351
-
352
- const prevColorTexNode = this.prevColorTexNode;
353
- const prevNormalDepthTexNode = this.prevNormalDepthTexNode;
354
- const prevAlbedoTexNode = this.prevAlbedoTexNode;
355
-
356
- const computeFn = Fn( () => {
357
-
358
- // Map thread to image-space pixel via tile offset
359
- const gx = tileOffsetX.add( int( workgroupId.x ).mul( WG_SIZE ) ).add( int( localId.x ) );
360
- const gy = tileOffsetY.add( int( workgroupId.y ).mul( WG_SIZE ) ).add( int( localId.y ) );
361
-
362
- // Bounds check only needed for edge workgroups that overshoot render dimensions
363
- If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
364
-
365
- const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
366
-
367
- pathTracerMain( {
368
- pixelCoord,
369
- writeColorTex, writeNDTex, writeAlbedoTex,
370
- // Previous-frame textures from MRT RenderTarget (sampled via texture())
371
- prevAccumTexture: prevColorTexNode,
372
- prevNormalDepthTexture: prevNormalDepthTexNode,
373
- prevAlbedoTexture: prevAlbedoTexNode,
374
- resolution: stage.resolution,
375
- frame: stage.frame,
376
- samplesPerPixel: stage.samplesPerPixel,
377
- visMode: stage.visMode,
378
- cameraWorldMatrix: stage.cameraWorldMatrix,
379
- cameraProjectionMatrixInverse: stage.cameraProjectionMatrixInverse,
380
- cameraViewMatrix: stage.cameraViewMatrix,
381
- cameraProjectionMatrix: stage.cameraProjectionMatrix,
382
- bvhBuffer: bvhStorage,
383
- triangleBuffer: triStorage,
384
- materialBuffer: matStorage,
385
- albedoMaps: albedoMapsTex,
386
- normalMaps: normalMapsTex,
387
- bumpMaps: bumpMapsTex,
388
- metalnessMaps: metalnessMapsTex,
389
- roughnessMaps: roughnessMapsTex,
390
- emissiveMaps: emissiveMapsTex,
391
- displacementMaps: displacementMapsTex,
392
- directionalLightsBuffer: stage.directionalLightsBufferNode,
393
- numDirectionalLights: stage.numDirectionalLights,
394
- areaLightsBuffer: stage.areaLightsBufferNode,
395
- numAreaLights: stage.numAreaLights,
396
- pointLightsBuffer: stage.pointLightsBufferNode,
397
- numPointLights: stage.numPointLights,
398
- spotLightsBuffer: stage.spotLightsBufferNode,
399
- numSpotLights: stage.numSpotLights,
400
- envTexture: envTex,
401
- environmentIntensity: stage.environmentIntensity,
402
- envMatrix: stage.environmentMatrix,
403
- envCDFBuffer: envCDFStorage,
404
- envTotalSum: stage.envTotalSum,
405
- envCompensationDelta: stage.envCompensationDelta,
406
- envResolution: stage.envResolution,
407
- enableEnvironmentLight: stage.enableEnvironment,
408
- useEnvMapIS: stage.useEnvMapIS,
409
- groundProjectionEnabled: stage.groundProjectionEnabled,
410
- groundProjectionRadius: stage.groundProjectionRadius,
411
- groundProjectionHeight: stage.groundProjectionHeight,
412
- maxBounceCount: stage.maxBounces,
413
- transmissiveBounces: stage.transmissiveBounces,
414
- maxSubsurfaceSteps: stage.maxSubsurfaceSteps,
415
- showBackground: stage.showBackground,
416
- transparentBackground: stage.transparentBackground,
417
- backgroundIntensity: stage.backgroundIntensity,
418
- fireflyThreshold: stage.fireflyThreshold,
419
- globalIlluminationIntensity: stage.globalIlluminationIntensity,
420
- enableEmissiveTriangleSampling: stage.enableEmissiveTriangleSampling,
421
- emissiveTriangleBuffer: lightBufferStorage,
422
- emissiveTriangleCount: stage.emissiveTriangleCount,
423
- emissiveTotalPower: stage.emissiveTotalPower,
424
- emissiveBoost: stage.emissiveBoost,
425
- emissiveVec4Offset: stage.emissiveVec4Offset,
426
- lightBVHBuffer: lightBufferStorage,
427
- lightBVHNodeCount: stage.lightBVHNodeCount,
428
- debugVisScale: stage.debugVisScale,
429
- enableAccumulation: stage.enableAccumulation,
430
- hasPreviousAccumulated: stage.hasPreviousAccumulated,
431
- accumulationAlpha: stage.accumulationAlpha,
432
- cameraIsMoving: stage.cameraIsMoving,
433
- useAdaptiveSampling: stage.useAdaptiveSampling,
434
- adaptiveSamplingTexture: adaptiveSamplingTex,
435
- adaptiveSamplingMin: stage.adaptiveSamplingMin,
436
- adaptiveSamplingMax: stage.adaptiveSamplingMax,
437
- enableDOF: stage.enableDOF,
438
- focalLength: stage.focalLength,
439
- aperture: stage.aperture,
440
- focusDistance: stage.focusDistance,
441
- sceneScale: stage.sceneScale,
442
- apertureScale: stage.apertureScale,
443
- anamorphicRatio: stage.anamorphicRatio,
444
- } );
445
-
446
- } );
447
-
448
- } );
449
-
450
- return computeFn().compute(
451
- [ this._dispatchX, this._dispatchY, 1 ],
452
- [ WG_SIZE, WG_SIZE, 1 ]
453
- );
454
-
455
- }
456
-
457
156
  dispose() {
458
157
 
459
- this.computeNode?.dispose();
460
-
461
- this.computeNode = null;
462
158
  this.prevColorTexNode = null;
463
159
  this.prevNormalDepthTexNode = null;
464
160
  this.prevAlbedoTexNode = null;
465
- this.adaptiveSamplingTexNode = null;
466
161
  this._sceneTextureNodes = null;
467
- this._compiled = false;
468
162
 
469
163
  }
470
164
 
@@ -13,11 +13,12 @@
13
13
  */
14
14
 
15
15
  import { StorageTexture, RenderTarget } from 'three/webgpu';
16
- import { RGBAFormat, FloatType, LinearFilter, NearestFilter } from 'three';
16
+ import { RGBAFormat, FloatType, LinearFilter, NearestFilter, Box2, Vector2 } from 'three';
17
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
17
18
 
18
- function createWriteStorageTex( width, height ) {
19
+ function createWriteStorageTex() {
19
20
 
20
- const tex = new StorageTexture( width, height );
21
+ const tex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
21
22
  tex.type = FloatType;
22
23
  tex.format = RGBAFormat;
23
24
  tex.minFilter = LinearFilter;
@@ -44,6 +45,9 @@ export class StorageTexturePool {
44
45
  this.renderWidth = 0;
45
46
  this.renderHeight = 0;
46
47
 
48
+ // Reused srcRegion for copies out of the over-allocated StorageTextures.
49
+ this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
50
+
47
51
  if ( width > 0 && height > 0 ) {
48
52
 
49
53
  this.create( width, height );
@@ -59,10 +63,10 @@ export class StorageTexturePool {
59
63
  this.renderWidth = width;
60
64
  this.renderHeight = height;
61
65
 
62
- // Write-only StorageTextures
63
- this.writeColor = createWriteStorageTex( width, height );
64
- this.writeNormalDepth = createWriteStorageTex( width, height );
65
- this.writeAlbedo = createWriteStorageTex( width, height );
66
+ // Write-only StorageTextures allocated at max — never resized (see resize crash fix).
67
+ this.writeColor = createWriteStorageTex();
68
+ this.writeNormalDepth = createWriteStorageTex();
69
+ this.writeAlbedo = createWriteStorageTex();
66
70
 
67
71
  // Readable MRT RenderTarget (3 color attachments, no depth/stencil)
68
72
  this.readTarget = new RenderTarget( width, height, {
@@ -85,13 +89,22 @@ export class StorageTexturePool {
85
89
 
86
90
  ensureSize( width, height ) {
87
91
 
88
- if ( this.renderWidth !== width || this.renderHeight !== height || ! this.writeColor ) {
92
+ if ( ! this.writeColor ) {
89
93
 
90
94
  this.create( width, height );
91
95
  return true;
92
96
 
93
97
  }
94
98
 
99
+ if ( this.renderWidth !== width || this.renderHeight !== height ) {
100
+
101
+ // Resize only the readTarget — never dispose/recreate the write StorageTextures
102
+ // (that destroys the texture the compute bind group holds → submit crash).
103
+ this.setSize( width, height );
104
+ return true;
105
+
106
+ }
107
+
95
108
  return false;
96
109
 
97
110
  }
@@ -131,9 +144,13 @@ export class StorageTexturePool {
131
144
  */
132
145
  copyToReadTargets( renderer ) {
133
146
 
134
- renderer.copyTextureToTexture( this.writeColor, this.readTarget.textures[ 0 ] );
135
- renderer.copyTextureToTexture( this.writeNormalDepth, this.readTarget.textures[ 1 ] );
136
- renderer.copyTextureToTexture( this.writeAlbedo, this.readTarget.textures[ 2 ] );
147
+ // Source write StorageTextures are over-allocated at the max size; the Box2 region
148
+ // restricts the copy to the active render size so source and destination extents match.
149
+ this._srcRegion.max.set( this.renderWidth, this.renderHeight );
150
+
151
+ renderer.copyTextureToTexture( this.writeColor, this.readTarget.textures[ 0 ], this._srcRegion );
152
+ renderer.copyTextureToTexture( this.writeNormalDepth, this.readTarget.textures[ 1 ], this._srcRegion );
153
+ renderer.copyTextureToTexture( this.writeAlbedo, this.readTarget.textures[ 2 ], this._srcRegion );
137
154
 
138
155
  }
139
156
 
@@ -163,10 +180,7 @@ export class StorageTexturePool {
163
180
  this.renderWidth = width;
164
181
  this.renderHeight = height;
165
182
 
166
- this.writeColor?.setSize( width, height );
167
- this.writeNormalDepth?.setSize( width, height );
168
- this.writeAlbedo?.setSize( width, height );
169
-
183
+ // Write StorageTextures stay at max allocation — never resized (see resize crash fix).
170
184
  if ( this.readTarget ) {
171
185
 
172
186
  this.readTarget.setSize( width, height );