rayzee 9.4.2 → 9.5.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 (144) hide show
  1. package/README.md +223 -21
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
  6. package/dist/ColorManagement-CheBCPaW.js +1488 -0
  7. package/dist/EngineDefaults-DH3bI5t-.js +844 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-BnHghprG.js +97 -0
  12. package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
  13. package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
  14. package/dist/PathTracer-Ccl9KOuF.js +29729 -0
  15. package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
  16. package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
  17. package/dist/SolarLimb-ken-JbDK.js +721 -0
  18. package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
  19. package/dist/ViewTransforms-D0rTLBsX.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-BZw7TiDp.js +75 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2266 -44079
  33. package/dist/rayzee.umd.js +485 -473
  34. package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/EngineDefaults.js +22 -8
  41. package/src/EngineEvents.js +2 -0
  42. package/src/EngineIssues.js +1 -0
  43. package/src/PathTracerApp.js +617 -4016
  44. package/src/Pipeline/PipelineContext.js +0 -4
  45. package/src/Platform.js +36 -0
  46. package/src/Processor/ArchiveCache.js +1 -1
  47. package/src/Processor/ArchiveImporter.js +1259 -0
  48. package/src/Processor/AssetLoader.js +322 -1746
  49. package/src/Processor/AtmosphereModel.js +1 -2
  50. package/src/Processor/BVHBuilder.js +4 -95
  51. package/src/Processor/EnvironmentExactTable.js +187 -0
  52. package/src/Processor/EquirectHDRInfo.js +30 -223
  53. package/src/Processor/FileFormats.js +116 -0
  54. package/src/Processor/GLTFDecoders.js +85 -0
  55. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  56. package/src/Processor/GeometryExtractor.js +12 -1
  57. package/src/Processor/IESParser.js +47 -35
  58. package/src/Processor/KernelManager.js +35 -2
  59. package/src/Processor/MaterialPacking.js +8 -7
  60. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  61. package/src/Processor/PBRT/PBRTParser.js +30 -17
  62. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  63. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  64. package/src/Processor/PBRT/PFM.js +59 -0
  65. package/src/Processor/PackedRayBuffer.js +116 -9
  66. package/src/Processor/ParallelBVHBuilder.js +3 -6
  67. package/src/Processor/PhysicalSky.js +5 -4
  68. package/src/Processor/PlatformImageLoader.js +50 -6
  69. package/src/Processor/QueueManager.js +12 -3
  70. package/src/Processor/ReinsertionOptimizer.js +2 -2
  71. package/src/Processor/SceneProcessor.js +15 -55
  72. package/src/Processor/ShaderBuilder.js +6 -16
  73. package/src/Processor/SolarLimb.js +3 -0
  74. package/src/Processor/TextureCreator.js +1 -1
  75. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  76. package/src/Processor/Workers/BVHWorker.js +5 -18
  77. package/src/Processor/Workers/CDFWorker.js +6 -183
  78. package/src/Processor/archiveFormats.js +7 -0
  79. package/src/Processor/utils.js +2 -1
  80. package/src/RayzeeRenderer.js +3902 -0
  81. package/src/RenderSettings.js +49 -41
  82. package/src/Stages/ASVGF.js +5 -7
  83. package/src/Stages/AutoExposure.js +3 -12
  84. package/src/Stages/Compositor.js +8 -23
  85. package/src/Stages/NRD.js +9 -2
  86. package/src/Stages/NormalDepth.js +9 -6
  87. package/src/Stages/PathTracer.js +254 -38
  88. package/src/Stages/PathTracerStage.js +11 -16
  89. package/src/Storage/BLASCache.js +1 -1
  90. package/src/Storage/CDFCache.js +18 -17
  91. package/src/Storage/DownloadCache.js +1 -1
  92. package/src/Storage/SceneGraphCodec.js +46 -3
  93. package/src/Storage/SpillStore.js +1 -1
  94. package/src/Storage/StorageManager.js +2 -4
  95. package/src/Storage/areas.js +4 -0
  96. package/src/TSL/BVHTraversal.js +62 -24
  97. package/src/TSL/Bidirectional.js +204 -0
  98. package/src/TSL/BidirectionalLamps.js +132 -0
  99. package/src/TSL/Clearcoat.js +15 -6
  100. package/src/TSL/Common.js +19 -14
  101. package/src/TSL/ConnectKernel.js +147 -0
  102. package/src/TSL/Debugger.js +1 -1
  103. package/src/TSL/Displacement.js +4 -3
  104. package/src/TSL/EmissiveSampling.js +64 -17
  105. package/src/TSL/Environment.js +77 -106
  106. package/src/TSL/EnvironmentCDF.js +85 -39
  107. package/src/TSL/ExtendKernel.js +8 -3
  108. package/src/TSL/FinalWriteKernel.js +4 -4
  109. package/src/TSL/GenerateKernel.js +18 -4
  110. package/src/TSL/HitFacet.js +35 -5
  111. package/src/TSL/LightBVHSampling.js +40 -9
  112. package/src/TSL/LightGenerateKernel.js +293 -0
  113. package/src/TSL/LightGuide.js +198 -0
  114. package/src/TSL/LightSplatKernel.js +165 -0
  115. package/src/TSL/LightsCore.js +13 -38
  116. package/src/TSL/LightsDirect.js +28 -29
  117. package/src/TSL/LightsIndirect.js +3 -1
  118. package/src/TSL/LightsSampling.js +90 -38
  119. package/src/TSL/MaterialEvaluation.js +113 -31
  120. package/src/TSL/MaterialProperties.js +44 -17
  121. package/src/TSL/MaterialTransmission.js +52 -47
  122. package/src/TSL/MergeKernel.js +218 -0
  123. package/src/TSL/PathTracerCore.js +65 -29
  124. package/src/TSL/Random.js +4 -0
  125. package/src/TSL/SceneResources.js +51 -0
  126. package/src/TSL/ShadeKernel.js +881 -103
  127. package/src/TSL/Struct.js +10 -0
  128. package/src/TSL/Sun.js +1 -1
  129. package/src/TSL/TextureSampling.js +65 -60
  130. package/src/addons/archives.js +14 -0
  131. package/src/addons/bidirectional.js +13 -0
  132. package/src/addons/color.js +32 -0
  133. package/src/addons/formats.js +16 -0
  134. package/src/addons/physicalSky.js +14 -0
  135. package/src/addons/storage.js +19 -0
  136. package/src/core.js +35 -0
  137. package/src/index.js +3 -1
  138. package/src/integrators/BidirectionalIntegrator.js +832 -0
  139. package/src/managers/EnvironmentManager.js +85 -33
  140. package/src/managers/MaterialDataManager.js +42 -0
  141. package/src/managers/UniformManager.js +0 -1
  142. package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
  143. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  144. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -7,7 +7,9 @@
7
7
  import { uniform, texture, storage } from 'three/tsl';
8
8
  import { gpuOnlyStorageAttribute } from '../TSL/patches.js';
9
9
  import { PathTracerStage } from './PathTracerStage.js';
10
- import { PackedRayBuffer, GBUFFER_STRIDE, RAY_STRIDE, HIT_STRIDE, freeStorageAttribute } from '../Processor/PackedRayBuffer.js';
10
+ import {
11
+ PackedRayBuffer, GBUFFER_STRIDE, RAY_STRIDE, HIT_STRIDE, freeStorageAttribute,
12
+ } from '../Processor/PackedRayBuffer.js';
11
13
  import { QueueManager, COUNTER, ENERGY_SCALE } from '../Processor/QueueManager.js';
12
14
  import { VRAMTracker } from '../Processor/VRAMTracker.js';
13
15
  import { KernelManager } from '../Processor/KernelManager.js';
@@ -17,13 +19,14 @@ import { buildShadeKernel, SHADE_WG_SIZE } from '../TSL/ShadeKernel.js';
17
19
  import { buildCompactKernel, buildCompactSubgroupKernel, COMPACT_WG_SIZE } from '../TSL/CompactKernel.js';
18
20
  import { buildFinalWriteKernel, FINALWRITE_WG_SIZE } from '../TSL/FinalWriteKernel.js';
19
21
  import { buildDebugKernel, DEBUG_WG_SIZE } from '../TSL/DebugKernel.js';
20
- import { setMaterialBucketTextures, buildBucketTextureNodes, refreshBucketTextureNodes } from '../TSL/TextureSampling.js';
21
- import { setShadowAlbedoMaps } from '../TSL/LightsDirect.js';
22
+ import { buildBucketTextureNodes, refreshBucketTextureNodes } from '../TSL/TextureSampling.js';
23
+ import { withSceneResources, MATERIAL_LAYERS } from '../TSL/SceneResources.js';
22
24
  import {
23
25
  buildResetGlobalHistKernel, buildGlobalHistKernel, buildGlobalPrefixKernel, buildGlobalScatterKernel,
24
26
  SORT_GLOBAL_WG_SIZE, SORT_GLOBAL_MAX_BINS,
25
27
  } from '../TSL/SortGlobalKernels.js';
26
28
  import { ENGINE_DEFAULTS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
29
+ import { ISSUE_CODES } from '../EngineIssues.js';
27
30
  import { createLogger, fmt } from '../utils/Logger.js';
28
31
  import { deviceMemoryGB } from '../Processor/HostMemory.js';
29
32
  import {
@@ -33,11 +36,11 @@ import {
33
36
  const log = createLogger( 'wavefront' );
34
37
 
35
38
  // Shared by the 1D index-list kernels built inline below.
36
- const LIST_WG_SIZE = 256;
39
+ export const LIST_WG_SIZE = 256;
37
40
 
38
41
  // Resized per bounce iteration, each from its own registered workgroup size. Unregistered entries
39
42
  // (the sort passes when _sortMaterials is off) are skipped by setDispatchForCount.
40
- const BOUNCE_KERNELS = [ 'extend', 'shade', 'globalHist', 'globalScatter', 'compact', 'compactCopyback' ];
43
+ export const BOUNCE_KERNELS = [ 'extend', 'shade', 'connect', 'merge', 'globalHist', 'globalScatter', 'compact', 'compactCopyback', 'lightCopyback' ];
41
44
 
42
45
  export class PathTracer extends PathTracerStage {
43
46
 
@@ -58,6 +61,9 @@ export class PathTracer extends PathTracerStage {
58
61
  this._convDebugSource = null; // memoized handle set for the Compositor's convergence overlay
59
62
  this._dilateFrozenUniform = uniform( 1, 'int' ); // 1 = dilate (default); 0 = plain per-pixel freeze
60
63
  this._wavefrontReady = false;
64
+ // The background compile of the kernels last built (_compileKernels), and its build's number.
65
+ this._kernelsCompiling = null;
66
+ this._kernelGeneration = 0;
61
67
 
62
68
  // Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. When no denoiser is active the
63
69
  // wavefront skips those writes (Generate/Shade G-buffer + FinalWrite stores). Gated by a live
@@ -142,6 +148,18 @@ export class PathTracer extends PathTracerStage {
142
148
  this._chunkRows = 0; // rows per chunk = floor(B / renderWidth), clamped ≥1
143
149
  this._numChunks = 1; // ceil(renderHeight / chunkRows)
144
150
 
151
+ // Outputs a later stage asked for (requestOutput), compiled into Shade only while asked.
152
+ this._outputs = new Map();
153
+ this._outputsChanged = false;
154
+ // The material layers the kernels were compiled with (_layersToCompile).
155
+ this._compiledLayers = null;
156
+
157
+ // Integrators other than the path tracer's own, by name (registerIntegrator); the chosen one's instance, or
158
+ // null for plain path tracing, which builds exactly the unidirectional kernels.
159
+ this._integratorFactories = new Map();
160
+ this._integratorInstances = new Map();
161
+ this._integrator = null;
162
+
145
163
  // VRAM accounting — providers are thunks reading CURRENT live resources,
146
164
  // so they survive buffer/texture reallocation (resize, scene/material reload).
147
165
  this.vramTracker = new VRAMTracker( this.renderer );
@@ -159,10 +177,13 @@ export class PathTracer extends PathTracerStage {
159
177
  t.register( 'rays', () => {
160
178
 
161
179
  const a = this._packedBuffers?._attrs;
162
- return a ? [ a.ray, a.rng, a.hit ] : null;
180
+ return a ? [ a.ray, a.hit ] : null;
163
181
 
164
182
  } );
165
183
 
184
+ // An integrator's own buffers (a light vertex cache rides in the hit buffer, under 'rays')
185
+ t.register( 'integrator', () => this._integrator?.gpuResources() ?? null );
186
+
166
187
  // Queue indices + atomic counters
167
188
  t.register( 'queues', () => {
168
189
 
@@ -236,14 +257,112 @@ export class PathTracer extends PathTracerStage {
236
257
 
237
258
  }
238
259
 
260
+ /**
261
+ * Asks for an extra per-pixel output, compiled in only while someone asks for it; the kernels rebuild before the
262
+ * next frame. `'gBuffer'`: the denoisers' normal/depth (`pathtracer:normalDepth`) and albedo (`pathtracer:albedo`),
263
+ * written while setAuxGBufferEnabled( true ). `'hitDistance'`: the first bounce's segment plus any alpha-skip run,
264
+ * at camera depth 1, as `encode( distance, viewZ )` returns it — a [0, 1] value, in `pathtracer:albedo.w`; it
265
+ * brings the G-buffer with it.
266
+ * @param {'gBuffer'|'hitDistance'} name
267
+ * @param {{encode?: function(Node, Node): Node}} [options]
268
+ * @returns {function(): void} withdraws the request
269
+ */
270
+ requestOutput( name, options = {} ) {
271
+
272
+ this._outputs.set( name, options );
273
+ this._outputsChanged = true;
274
+
275
+ return () => {
276
+
277
+ if ( this._outputs.get( name ) !== options ) return;
278
+ this._outputs.delete( name );
279
+ this._outputsChanged = true;
280
+
281
+ };
282
+
283
+ }
284
+
285
+ /**
286
+ * Makes an integrator choosable by name. `create( pathTracer )` makes its instance, once, which then takes the
287
+ * integrator hooks this class calls (see BidirectionalIntegrator); one class may answer several names.
288
+ * @param {string|string[]} names
289
+ * @param {function(PathTracer): Object} create
290
+ */
291
+ registerIntegrator( names, create ) {
292
+
293
+ for ( const name of [ names ].flat() ) this._integratorFactories.set( name, create );
294
+
295
+ }
296
+
297
+ /**
298
+ * Switching rebuilds the kernels: only an integrator's own bind its buffers. 'path' is the path tracer's own;
299
+ * the others must have been registered (rayzee/addons/bidirectional: 'bidirectional', 'vcm').
300
+ * @param {string} name
301
+ */
302
+ setIntegrator( name ) {
303
+
304
+ let next = null;
305
+ if ( name !== 'path' ) {
306
+
307
+ const create = this._integratorFactories.get( name );
308
+ if ( ! create ) {
309
+
310
+ this.issues?.record( ISSUE_CODES.CAPABILITY_MISSING, `integrator "${name}" is not installed: pathTracer.registerIntegrator(), or rayzee/addons/bidirectional`, { capability: name } );
311
+ return;
312
+
313
+ }
314
+
315
+ next = this._integratorInstances.get( create );
316
+ if ( ! next ) this._integratorInstances.set( create, next = create( this ) );
317
+
318
+ }
319
+
320
+ if ( next === this._integrator && ( ! next || next.name === name ) ) return;
321
+ if ( this._integrator && this._integrator !== next ) this._integrator.dispose();
322
+ next?.select( name );
323
+ this._integrator = next;
324
+ this._pathBudget = 0;
325
+
326
+ if ( this._packedBuffers && this.materialData?.materialCount > 0 ) {
327
+
328
+ if ( this._kernelManager ) this._kernelManager.dispose();
329
+ this._wavefrontReady = false;
330
+ this._buildWavefrontKernels();
331
+
332
+ }
333
+
334
+ }
335
+
336
+ get integrator() {
337
+
338
+ return this._integrator?.name ?? 'path';
339
+
340
+ }
341
+
342
+ /** The chosen integrator's instance — its controls, e.g. setStrategy() — or null for plain path tracing. */
343
+ get activeIntegrator() {
344
+
345
+ return this._integrator;
346
+
347
+ }
348
+
239
349
  render( context ) {
240
350
 
241
351
  // Kernels not built yet (first frame / mid-resize) — skip until ready.
242
352
  if ( ! this.isReady || ! this._wavefrontReady ) return;
243
353
 
244
354
  // The packed light buffer was grow-reallocated at runtime (emissive set grew) or a light list grew —
245
- // the compiled kernels still bind the old one, so rebuild before rendering.
246
- if ( this._lightBufferRealloc ) {
355
+ // the compiled kernels still bind the old one, so rebuild before rendering. Likewise a changed output request.
356
+ // A layer some material now uses is compiled in at once; one nothing uses any more waits for the next build.
357
+ if ( this._materialFeaturesChanged ) {
358
+
359
+ this._materialFeaturesChanged = false;
360
+ const needed = this._layersToCompile();
361
+ if ( MATERIAL_LAYERS.some( ( layer ) => needed[ layer ] && ! this._compiledLayers?.[ layer ] ) ) this._outputsChanged = true;
362
+
363
+ }
364
+
365
+ if ( this._lightBufferRealloc || this._outputsChanged ) {
247
366
 
248
367
  if ( this._kernelManager ) this._kernelManager.dispose();
249
368
  this._wavefrontReady = false;
@@ -292,7 +411,7 @@ export class PathTracer extends PathTracerStage {
292
411
  }
293
412
 
294
413
  this._handleResize();
295
- this.manageASVGFForRenderMode( renderMode );
414
+ this.resetHistoryOnRenderModeChange( renderMode );
296
415
 
297
416
  // Full-frame render is always a complete cycle (PER_CYCLE stages gate on this).
298
417
  if ( context ) context.setState( 'tileRenderingComplete', true );
@@ -390,6 +509,9 @@ export class PathTracer extends PathTracerStage {
390
509
  : Math.min( this._lastBounceCountsBudget, this._lastBounceCountsLoopBound ) )
391
510
  : 0;
392
511
 
512
+ // An integrator's light subpaths first, through the same pool; every camera chunk then connects to them.
513
+ const integrator = this._integrator?.beginFrame( loopBound ) ? this._integrator : null;
514
+
393
515
  // Blender-style row-band streaming: the fixed-budget path pool processes the image in bands of ≤ chunkRows
394
516
  // rows (one chunk when the frame fits the budget → identical to the pre-chunking path). See spec.
395
517
  for ( let chunkIndex = 0, rowBase = 0; rowBase < frameHeight; rowBase += chunkRows, chunkIndex ++ ) {
@@ -492,7 +614,9 @@ export class PathTracer extends PathTracerStage {
492
614
 
493
615
  }
494
616
 
617
+ integrator?.beforeShade();
495
618
  km.dispatch( 'shade' ); // shade thread 0 folds resetActiveCounter (zeroes ACTIVE_RAY_COUNT before compact)
619
+ integrator?.afterShade( km );
496
620
  km.dispatch( 'compact' );
497
621
  if ( useFunctionalCompaction ) {
498
622
 
@@ -521,6 +645,7 @@ export class PathTracer extends PathTracerStage {
521
645
 
522
646
  }
523
647
 
648
+ integrator?.resolve( km );
524
649
  km.dispatch( 'finalWrite' );
525
650
 
526
651
  }
@@ -723,17 +848,50 @@ export class PathTracer extends PathTracerStage {
723
848
  }
724
849
 
725
850
  /**
726
- * While a lockstep readback is due and has not landed, render() traces nothing: await this
727
- * before the next frame. Null otherwise.
851
+ * While the kernels compile in the background, or a lockstep readback is due and has not landed, render() traces
852
+ * nothing: await this before the next frame. Null otherwise.
728
853
  * @returns {?Promise<void>}
729
854
  */
730
855
  readbackWait() {
731
856
 
857
+ if ( this._kernelsCompiling ) return this._kernelsCompiling;
732
858
  const pending = this._lockstepRead;
733
859
  return pending && ! pending.apply && this.frameCount >= pending.due ? pending.read : null;
734
860
 
735
861
  }
736
862
 
863
+ /**
864
+ * Compiles the kernels just built without blocking the page. Until they are ready render() traces nothing and the
865
+ * canvas keeps its last frame; every driver loop awaits readbackWait(), which returns this. A build started
866
+ * meanwhile supersedes it. A failed background compile — and every compile in Node — leaves each kernel to compile
867
+ * at its first dispatch.
868
+ */
869
+ _compileKernels() {
870
+
871
+ const generation = ++ this._kernelGeneration;
872
+ if ( ! KernelManager.canCompileInBackground ) {
873
+
874
+ this._kernelsCompiling = null;
875
+ this._wavefrontReady = true;
876
+ return;
877
+
878
+ }
879
+
880
+ const kernels = this._kernelManager;
881
+ this.emit( 'pathtracer:compiling', { compiling: true } );
882
+ this._kernelsCompiling = kernels.compile()
883
+ .catch( ( error ) => log.warn( `background kernel compile failed — compiling at first dispatch: ${error?.message ?? error}` ) )
884
+ .then( () => {
885
+
886
+ if ( generation !== this._kernelGeneration ) return;
887
+ this._kernelsCompiling = null;
888
+ this._wavefrontReady = true;
889
+ this.emit( 'pathtracer:compiling', { compiling: false } );
890
+
891
+ } );
892
+
893
+ }
894
+
737
895
  /**
738
896
  * Per-pixel convergence state for the Compositor's debug overlay: the buffers FinalWrite maintains
739
897
  * plus the uniforms needed to re-derive its predicate. Read-only — the overlay never writes back.
@@ -798,11 +956,12 @@ export class PathTracer extends PathTracerStage {
798
956
  }
799
957
 
800
958
  // Aux MRT (normalDepth/albedo) is needed only by the denoiser/OIDN; DenoisingManager calls this to
801
- // turn the wavefront's aux writes on/off. It's a live uniform, so toggling is just a value flip —
802
- // no kernel rebuild, no UI freeze.
959
+ // turn the wavefront's aux writes on/off. The first call that turns them on compiles them in (requestOutput,
960
+ // kept for good); after that it is a live uniform, so toggling is a value flip — no kernel rebuild, no UI freeze.
803
961
  setAuxGBufferEnabled( enabled ) {
804
962
 
805
963
  enabled = !! enabled;
964
+ if ( enabled && ! this._outputs.has( 'gBuffer' ) ) this.requestOutput( 'gBuffer' );
806
965
  if ( this._auxGBufferEnabled === enabled ) return;
807
966
  this._auxGBufferEnabled = enabled;
808
967
  this._auxGBufferUniform.value = enabled ? 1 : 0;
@@ -888,7 +1047,8 @@ export class PathTracer extends PathTracerStage {
888
1047
 
889
1048
  _bounceLoopBound() {
890
1049
 
891
- return this.maxBounces.value + this.transmissiveBounces.value + this.maxSubsurfaceSteps.value;
1050
+ // Camera paths take one segment past their last bounce (ShadeKernel, EMISSION_ONLY).
1051
+ return this.maxBounces.value + this.transmissiveBounces.value + this.maxSubsurfaceSteps.value + 1;
892
1052
 
893
1053
  }
894
1054
 
@@ -942,12 +1102,13 @@ export class PathTracer extends PathTracerStage {
942
1102
  const budget = this.maxBounces.value;
943
1103
  const measuredLoopBound = this._bounceLoopBound();
944
1104
  const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
1105
+ const lightKey = this._integrator?.curveKey ?? null;
945
1106
  this.renderer.getArrayBufferAsync( attr ).then( ( buf ) => {
946
1107
 
947
1108
  // Drop counts measured at a now-stale generation (a resize or camera move happened mid-flight).
948
1109
  // A surviving readback was initiated while settled (init is skipped mid-motion) and no motion
949
1110
  // happened before it resolved, so its counts match the current view — safe to size from.
950
- if ( gen === this._readbackGeneration ) this._applyBounceCounts( buf, budget, measuredLoopBound, n );
1111
+ if ( gen === this._readbackGeneration ) this._applyBounceCounts( buf, budget, measuredLoopBound, n, lightKey );
951
1112
 
952
1113
  this._readbackPending = false;
953
1114
 
@@ -971,7 +1132,7 @@ export class PathTracer extends PathTracerStage {
971
1132
  // Full-FRAME pixel count (CONVERGED_COUNT sums across all chunks) — not _wfMaxRayCount, which
972
1133
  // after the chunk loop holds only the last band's pixel count.
973
1134
  const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
974
- this.renderer.getArrayBufferAsync( cAttr ).then( ( buf ) => {
1135
+ this.renderer.getArrayBufferAsync( cAttr, null, 0, COUNTER.COUNT * 4 ).then( ( buf ) => {
975
1136
 
976
1137
  if ( cgen === this._readbackGeneration && total > 0 ) this._applyCounters( buf, total );
977
1138
 
@@ -1004,17 +1165,18 @@ export class PathTracer extends PathTracerStage {
1004
1165
  const loopBound = this._bounceLoopBound();
1005
1166
  const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
1006
1167
  const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
1168
+ const lightKey = this._integrator?.curveKey ?? null;
1007
1169
 
1008
1170
  const entry = { due: this.frameCount + this._readbackEveryNFrames, read: null, apply: null };
1009
1171
  entry.read = Promise.all( [
1010
1172
  this.renderer.getArrayBufferAsync( bounceAttr ),
1011
- this.renderer.getArrayBufferAsync( counterAttr ),
1173
+ this.renderer.getArrayBufferAsync( counterAttr, null, 0, COUNTER.COUNT * 4 ),
1012
1174
  ] ).then( ( [ bounceBuf, counterBuf ] ) => {
1013
1175
 
1014
1176
  entry.apply = () => {
1015
1177
 
1016
1178
  if ( gen !== this._readbackGeneration ) return;
1017
- this._applyBounceCounts( bounceBuf, budget, loopBound, n );
1179
+ this._applyBounceCounts( bounceBuf, budget, loopBound, n, lightKey );
1018
1180
  if ( total > 0 ) this._applyCounters( counterBuf, total );
1019
1181
 
1020
1182
  };
@@ -1030,11 +1192,13 @@ export class PathTracer extends PathTracerStage {
1030
1192
 
1031
1193
  }
1032
1194
 
1033
- _applyBounceCounts( buf, budget, loopBound, n ) {
1195
+ _applyBounceCounts( buf, budget, loopBound, n, lightKey = null ) {
1034
1196
 
1035
1197
  const all = new Uint32Array( buf.slice( 0 ) );
1036
1198
  this._lastBounceCounts = all.subarray( 0, n );
1037
1199
  this._lastBounceEnergy = all.subarray( n, 2 * n );
1200
+ // An integrator's light pass keeps its own curve after the camera's.
1201
+ this._integrator?.applyLightCurve( all.subarray( 2 * n, 3 * n ), all.subarray( 3 * n, 4 * n ), lightKey );
1038
1202
  this._lastBounceCountsBudget = budget;
1039
1203
  this._lastBounceCountsLoopBound = loopBound;
1040
1204
  this._curveSizingValid = true;
@@ -1148,19 +1312,23 @@ export class PathTracer extends PathTracerStage {
1148
1312
  // any resolution through this pool, so a small B just means more row-bands (works on weak / small-VRAM GPUs).
1149
1313
  _computePathBudget() {
1150
1314
 
1151
- if ( this._pathBudgetOverride > 0 ) return this._pathBudgetOverride;
1152
-
1153
1315
  const RAY_BYTES = RAY_STRIDE * 16;
1154
- const bytesPerPath = RAY_STRIDE * 16 + HIT_STRIDE * 16 + 4 /* rng */ + GBUFFER_STRIDE * 16
1316
+ const HIT_BYTES = ( this._integrator?.hitStride ?? HIT_STRIDE ) * 16;
1317
+ const bytesPerPath = RAY_BYTES + HIT_BYTES + GBUFFER_STRIDE * 16
1155
1318
  + 4 + 4 /* activeIndices A/B */ + 4;
1156
1319
 
1157
1320
  const limits = this.renderer?.backend?.device?.limits;
1158
1321
  const maxBinding = limits?.maxStorageBufferBindingSize || ( 128 * 1024 * 1024 );
1159
1322
  const maxBuffer = limits?.maxBufferSize || maxBinding;
1160
-
1161
- const bByBinding = Math.floor( ( maxBinding * 0.9 ) / RAY_BYTES );
1162
1323
  const deviceMemBytes = deviceMemoryGB( this.hostMemoryGB ).gb * 1024 * 1024 * 1024;
1163
- const poolVramBudget = Math.min( deviceMemBytes * 0.25, maxBuffer * 4 );
1324
+
1325
+ // An integrator's light vertex cache shares the hit buffer's binding with the paths.
1326
+ const cacheBytes = this._integrator?.cacheBytes( maxBinding, deviceMemBytes ) ?? 0;
1327
+
1328
+ if ( this._pathBudgetOverride > 0 ) return this._pathBudgetOverride;
1329
+
1330
+ const bByBinding = Math.floor( Math.min( ( maxBinding * 0.9 ) / RAY_BYTES, ( maxBinding * 0.9 - cacheBytes ) / HIT_BYTES ) );
1331
+ const poolVramBudget = Math.min( deviceMemBytes * 0.25, maxBuffer * 4 ) - cacheBytes;
1164
1332
  const bByVram = Math.floor( poolVramBudget / bytesPerPath );
1165
1333
 
1166
1334
  const MIN_B = 512 * 512;
@@ -1219,6 +1387,22 @@ export class PathTracer extends PathTracerStage {
1219
1387
 
1220
1388
  }
1221
1389
 
1390
+ /** The material layers the scene uses; bidirectional leaves diffuse transmission out (its paths do not cross surfaces). */
1391
+ _layersToCompile() {
1392
+
1393
+ const layers = this.materialData.materialLayers();
1394
+ if ( this._integrator ) layers.diffuseTransmission = false;
1395
+ return layers;
1396
+
1397
+ }
1398
+
1399
+ /** Whether the kernels write the G-buffer: asked for directly, or for the hit distance that rides in it. */
1400
+ get _gBufferCompiled() {
1401
+
1402
+ return this._outputs.has( 'gBuffer' ) || this._outputs.has( 'hitDistance' );
1403
+
1404
+ }
1405
+
1222
1406
  _buildWavefrontKernels() {
1223
1407
 
1224
1408
  const texNodes = this.shaderBuilder.getSceneTextureNodes();
@@ -1226,6 +1410,8 @@ export class PathTracer extends PathTracerStage {
1226
1410
 
1227
1411
  // A fresh build binds the current lightStorageAttr — any pending realloc is covered.
1228
1412
  this._lightBufferRealloc = false;
1413
+ this._outputsChanged = false;
1414
+ this._integrator?.beforeKernelBuild();
1229
1415
 
1230
1416
  const w = this.storageTextures.renderWidth;
1231
1417
  const h = this.storageTextures.renderHeight;
@@ -1242,13 +1428,14 @@ export class PathTracer extends PathTracerStage {
1242
1428
 
1243
1429
  // Per-path buffers (RAY/HIT/rng) sized to the budget B and indexed by LOCAL slot r ∈ [0,B). _cap = B is
1244
1430
  // baked into the SoA stride but never changes with resolution, so this build happens once (model load).
1431
+ const lightVertices = this._integrator?.lightVertices ?? 0;
1245
1432
  if ( ! this._packedBuffers ) {
1246
1433
 
1247
- this._packedBuffers = new PackedRayBuffer( B, this.renderer );
1434
+ this._packedBuffers = new PackedRayBuffer( B, this.renderer, lightVertices );
1248
1435
 
1249
1436
  } else {
1250
1437
 
1251
- this._packedBuffers.resize( B );
1438
+ this._packedBuffers.resize( B, lightVertices );
1252
1439
 
1253
1440
  }
1254
1441
 
@@ -1286,6 +1473,8 @@ export class PathTracer extends PathTracerStage {
1286
1473
 
1287
1474
  }
1288
1475
 
1476
+ this._integrator?.allocate( maxPixels );
1477
+
1289
1478
  const m2RW = storage( this._m2Attr, 'float' );
1290
1479
  const streakRW = storage( this._streakAttr, 'uint' );
1291
1480
  const streakRO = storage( this._streakAttr, 'uint' ).toReadOnly();
@@ -1404,6 +1593,8 @@ export class PathTracer extends PathTracerStage {
1404
1593
  transmissiveBounces: this.transmissiveBounces,
1405
1594
  transparentBackground: this.transparentBackground,
1406
1595
  auxGBufferEnabled: this._auxGBufferUniform,
1596
+ gBuffer: this._gBufferCompiled,
1597
+ bidirectional: this._integrator?.uniforms ?? null,
1407
1598
  };
1408
1599
  const genFn = buildGenerateKernel( genParams );
1409
1600
  this._kernelManager.register( 'generate',
@@ -1597,13 +1788,21 @@ export class PathTracer extends PathTracerStage {
1597
1788
  const _mat = this.materialData;
1598
1789
  const _env = this.environment;
1599
1790
  // Consolidated size-bucket nodes (K sRGB + K linear). Empty buckets get placeholders so
1600
- // every runtime branch references a valid node. Published to the sampling module before
1601
- // the Shade/Debug graphs are built so they bake in these (per-pipeline) nodes.
1791
+ // every runtime branch references a valid node.
1602
1792
  const freshSrgbBuckets = buildBucketTextureNodes( _mat.srgbBuckets );
1603
1793
  const freshLinearBuckets = buildBucketTextureNodes( _mat.linearBuckets );
1604
- setMaterialBucketTextures( freshSrgbBuckets, freshLinearBuckets );
1605
- // Alpha-cutout shadow rays sample albedo (sRGB pool) — emitted into the shade graph now.
1606
- setShadowAlbedoMaps( freshSrgbBuckets );
1794
+ // This renderer's resources, in the build context of each kernel that samples them (SceneResources.js).
1795
+ // Alpha-cutout shadow rays sample albedo (sRGB pool).
1796
+ const resources = {
1797
+ srgbBuckets: freshSrgbBuckets,
1798
+ linearBuckets: freshLinearBuckets,
1799
+ shadowAlbedoMaps: freshSrgbBuckets,
1800
+ goboMaps: texNodes.goboMapsTex,
1801
+ iesProfiles: texNodes.iesProfilesTex,
1802
+ alphaShadows: this.uniforms.get( 'enableAlphaShadows' ),
1803
+ materialLayers: this._compiledLayers = this._layersToCompile(),
1804
+ };
1805
+ const own = ( call ) => withSceneResources( call, resources );
1607
1806
  const freshEnvTex = _env.environmentTexture ? texture( _env.environmentTexture ) : texNodes.envTex;
1608
1807
 
1609
1808
  this._wfTexNodes = {
@@ -1680,7 +1879,6 @@ export class PathTracer extends PathTracerStage {
1680
1879
 
1681
1880
  const shadeFn = buildShadeKernel( {
1682
1881
  gBufferRW,
1683
- envCompensationDelta: this.envCompensationDelta,
1684
1882
  bvhBuffer: freshBvh,
1685
1883
  triangleBuffer: freshTri,
1686
1884
  materialBuffer: freshMat,
@@ -1732,7 +1930,6 @@ export class PathTracer extends PathTracerStage {
1732
1930
  emissiveVec4Offset: this.emissiveVec4Offset,
1733
1931
  emissiveTotalPower: this.emissiveTotalPower,
1734
1932
  emissiveBoost: this.emissiveBoost,
1735
- totalTriangleCount: this.totalTriangleCount,
1736
1933
  enableEmissiveTriangleSampling: this.enableEmissiveTriangleSampling,
1737
1934
  lightBVHNodeCount: this.lightBVHNodeCount,
1738
1935
  reverseMapVec4Offset: this.reverseMapVec4Offset,
@@ -1740,13 +1937,17 @@ export class PathTracer extends PathTracerStage {
1740
1937
  maxRayCount: this._wfMaxRayCount,
1741
1938
  chunkRowBase: this._wfChunkRowBase,
1742
1939
  auxGBufferEnabled: this._auxGBufferUniform,
1940
+ gBuffer: this._gBufferCompiled,
1743
1941
  hasSun: this.hasSun,
1744
1942
  sunDirection: this.sunDirection,
1745
1943
  sunRadiance: this.sunRadiance,
1746
1944
  sunParams: this.sunParams,
1945
+ bidirectional: this._integrator?.uniforms ?? null,
1946
+ hitDistanceEncode: this._outputs.get( 'hitDistance' )?.encode ?? null,
1947
+ materialLayers: this._compiledLayers,
1747
1948
  } );
1748
1949
  this._kernelManager.register( 'shade',
1749
- shadeFn().compute(
1950
+ own( shadeFn() ).compute(
1750
1951
  [ Math.ceil( maxRays / SHADE_WG_SIZE ), 1, 1 ],
1751
1952
  [ SHADE_WG_SIZE, 1, 1 ]
1752
1953
  )
@@ -1812,6 +2013,11 @@ export class PathTracer extends PathTracerStage {
1812
2013
  copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
1813
2014
  );
1814
2015
 
2016
+ this._integrator?.registerKernels( {
2017
+ km: this._kernelManager, qm, pb, counters, bounceCountsBuf, wfCurrentBounce, copyReadB, copyWriteA, maxRays, w, own,
2018
+ freshLight, freshTri, freshBvh, freshMat, freshEnvTex, freshEnvCDF,
2019
+ } );
2020
+
1815
2021
  const fwFn = buildFinalWriteKernel( {
1816
2022
  rayBufferRO: pb.rayBuffer.ro,
1817
2023
  gBufferRO,
@@ -1834,6 +2040,7 @@ export class PathTracer extends PathTracerStage {
1834
2040
  renderHeight: this._wfRenderHeight,
1835
2041
  visMode: this.visMode,
1836
2042
  auxGBufferEnabled: this._auxGBufferUniform,
2043
+ gBuffer: this._gBufferCompiled,
1837
2044
  cleanAuxNormalEnabled: this._cleanAuxNormalUniform,
1838
2045
  m2BufferRW: m2RW,
1839
2046
  useAdaptiveSampling: this.useAdaptiveSampling,
@@ -1893,14 +2100,16 @@ export class PathTracer extends PathTracerStage {
1893
2100
  debugVisScale: this.debugVisScale,
1894
2101
  frame: this.frame,
1895
2102
  } );
2103
+ // Only the debug views dispatch it, so it compiles when one is first chosen.
1896
2104
  this._kernelManager.register( 'debug',
1897
- debugFn().compute(
2105
+ own( debugFn() ).compute(
1898
2106
  [ Math.ceil( w / DEBUG_WG_SIZE ), Math.ceil( h / DEBUG_WG_SIZE ), 1 ],
1899
2107
  [ DEBUG_WG_SIZE, DEBUG_WG_SIZE, 1 ]
1900
- )
2108
+ ),
2109
+ { eager: false },
1901
2110
  );
1902
2111
 
1903
- this._wavefrontReady = true;
2112
+ this._compileKernels();
1904
2113
 
1905
2114
  const bufferBytes = ( this._packedBuffers?.totalBytes ?? 0 ) + ( this._queueManager?.totalBytes ?? 0 );
1906
2115
 
@@ -1909,6 +2118,7 @@ export class PathTracer extends PathTracerStage {
1909
2118
  `${fmt.mb( bufferBytes )} wavefront buffers`,
1910
2119
  `budget ${fmt.n( B )} paths`,
1911
2120
  this._numChunks > 1 ? `${this._numChunks} chunks of ≤${this._chunkRows} rows` : null,
2121
+ this._integrator?.describe() ?? null,
1912
2122
  ] ) );
1913
2123
 
1914
2124
  }
@@ -1941,6 +2151,7 @@ export class PathTracer extends PathTracerStage {
1941
2151
  const km = this._kernelManager;
1942
2152
  km.setDispatchForGrid( 'generate', w, rows );
1943
2153
  km.setDispatchForGrid( 'finalWrite', w, rows );
2154
+ km.setDispatchForGrid( 'splatResolve', w, rows );
1944
2155
  km.setDispatchForCount( 'initActiveIndices', chunkPixels );
1945
2156
  km.setDispatchForCount( 'buildActivePixels', chunkPixels );
1946
2157
 
@@ -1958,6 +2169,9 @@ export class PathTracer extends PathTracerStage {
1958
2169
  this._m2Attr?.dispose?.();
1959
2170
  this._streakAttr?.dispose?.();
1960
2171
  this._frozenMaskAttr?.dispose?.();
2172
+ for ( const integrator of this._integratorInstances.values() ) integrator.dispose();
2173
+ this._integratorInstances.clear();
2174
+ this._integrator = null;
1961
2175
  this._packedBuffers = null;
1962
2176
  this._queueManager = null;
1963
2177
  this._kernelManager = null;
@@ -1967,6 +2181,8 @@ export class PathTracer extends PathTracerStage {
1967
2181
  this._frozenMaskAttr = null;
1968
2182
  this._convDebugSource = null;
1969
2183
  this._wavefrontReady = false;
2184
+ this._kernelGeneration ++;
2185
+ this._kernelsCompiling = null;
1970
2186
 
1971
2187
  }
1972
2188
 
@@ -53,8 +53,7 @@ const BVH_VEC4_PER_NODE = 4;
53
53
  * Events emitted:
54
54
  * - pathtracer:frameComplete - When a frame finishes rendering
55
55
  * - camera:moved - When camera position/orientation changes
56
- * - asvgf:reset - Request ASVGF to reset temporal data
57
- * - asvgf:updateParameters - Update ASVGF parameters
56
+ * - pipeline:historyReset - temporal history from before is not comparable (render mode change)
58
57
  *
59
58
  * Textures published to context:
60
59
  * - pathtracer:color - Main color output
@@ -113,6 +112,13 @@ export class PathTracerStage extends RenderStage {
113
112
  // Initialize material data manager
114
113
  this.materialData = new MaterialDataManager( this.sdfs );
115
114
  this.materialData.callbacks.onReset = () => this.reset();
115
+ // A material edit can switch the diffuse transmission lobe on or off, which the shade kernel compiles in.
116
+ this.materialData.callbacks.onMaterialFeaturesChanged = () => {
117
+
118
+ this._materialFeaturesChanged = true;
119
+
120
+ };
121
+
116
122
  // Material edits patch the per-triangle flags lane in the CPU records (`_triangleRecords`
117
123
  // when chunked, `_triangleFlat` otherwise — not sdfs.triangleData, which the PathTracerApp
118
124
  // build path never fills). The flags live in the shade rows, so only those go up again.
@@ -375,16 +381,6 @@ export class PathTracerStage extends RenderStage {
375
381
 
376
382
  } );
377
383
 
378
- this.on( 'pathtracer:setCompletionThreshold', ( data ) => {
379
-
380
- if ( data && data.threshold !== undefined ) {
381
-
382
- this.completionThreshold = data.threshold;
383
-
384
- }
385
-
386
- } );
387
-
388
384
  }
389
385
 
390
386
  // ===== PUBLIC API METHODS =====
@@ -542,7 +538,7 @@ export class PathTracerStage extends RenderStage {
542
538
  this.performanceMonitor?.reset();
543
539
 
544
540
  // lastRenderMode is deliberately NOT invalidated here — clearing it made
545
- // manageASVGFForRenderMode see a phantom mode change 50 ms after every reset, wiping
541
+ // resetHistoryOnRenderModeChange see a phantom mode change 50 ms after every reset, wiping
546
542
  // ASVGF's history. A real mode change is still caught by the renderMode comparison.
547
543
  this.lastInteractionModeState = false;
548
544
 
@@ -1420,7 +1416,7 @@ export class PathTracerStage extends RenderStage {
1420
1416
 
1421
1417
  // ===== ASVGF DENOISING MANAGEMENT =====
1422
1418
 
1423
- manageASVGFForRenderMode( renderMode ) {
1419
+ resetHistoryOnRenderModeChange( renderMode ) {
1424
1420
 
1425
1421
  if ( renderMode !== this.lastRenderMode ) {
1426
1422
 
@@ -1440,8 +1436,7 @@ export class PathTracerStage extends RenderStage {
1440
1436
  // History from the previous mode is not comparable. temporalAlpha is NOT
1441
1437
  // touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
1442
1438
  // with a hardcoded value meant `medium` and `high` never took effect.
1443
- this.emit( 'asvgf:reset' );
1444
- this.emit( 'denoiser:reset' );
1439
+ this.emit( 'pipeline:historyReset' );
1445
1440
 
1446
1441
  }
1447
1442
 
@@ -4,7 +4,7 @@
4
4
  * stored BLAS is only ever used for exactly the triangles it was built over.
5
5
  */
6
6
 
7
- import { ENGINE_AREAS } from './StorageManager.js';
7
+ import { ENGINE_AREAS } from './areas.js';
8
8
 
9
9
  export const BLAS_CACHE_FORMAT = 2;
10
10