rayzee 10.0.0 → 11.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 (176) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-Hn35o6Z5.js +5145 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-CBw8hkeu.js +946 -0
  6. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  7. package/dist/ColorManagement-oeEk7oSu.js +1488 -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-dDDGj9Av.js +97 -0
  12. package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BzNpiVHZ.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-Bk7bHg2B.js} +55 -120
  14. package/dist/PathTracer-72XufKAr.js +29976 -0
  15. package/dist/PhysicalSky-BgjdV88k.js +1219 -0
  16. package/dist/RayzeeRenderer-1QRO97eq.js +3551 -0
  17. package/dist/SolarLimb-CPaeZVwz.js +721 -0
  18. package/dist/ToneMapGPU-CjU3osDd.js +177 -0
  19. package/dist/ViewTransforms-CYN7bq2w.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-tnAoUL1j.js +94 -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 +2551 -44024
  33. package/dist/rayzee.umd.js +528 -487
  34. package/dist/sceneCachePolicy-C67jbci-.js +443 -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/Color/ViewTransforms.js +5 -1
  41. package/src/EngineDefaults.js +62 -1029
  42. package/src/EngineEvents.js +2 -0
  43. package/src/EngineIssues.js +2 -0
  44. package/src/Headless.js +6 -5
  45. package/src/Passes/AIUpscaler.js +58 -141
  46. package/src/Passes/OIDNDenoiser.js +92 -65
  47. package/src/Passes/OIDNTemporalHistory.js +1 -1
  48. package/src/PathTracerApp.js +620 -4026
  49. package/src/Pipeline/PipelineContext.js +0 -4
  50. package/src/Platform.js +36 -0
  51. package/src/Processor/ArchiveCache.js +20 -7
  52. package/src/Processor/ArchiveImporter.js +1265 -0
  53. package/src/Processor/ArchiveReader.js +22 -4
  54. package/src/Processor/AssetLoader.js +327 -1763
  55. package/src/Processor/AtmosphereModel.js +1 -2
  56. package/src/Processor/BVHBuilder.js +14 -126
  57. package/src/Processor/BVHLeafFold.js +4 -6
  58. package/src/Processor/BVHRefitter.js +67 -46
  59. package/src/Processor/BufferLayout.js +291 -0
  60. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  61. package/src/Processor/EnvironmentExactTable.js +187 -0
  62. package/src/Processor/EquirectHDRInfo.js +30 -223
  63. package/src/Processor/FileFormats.js +116 -0
  64. package/src/Processor/GLTFDecoders.js +85 -0
  65. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  66. package/src/Processor/GeometryExtractor.js +15 -5
  67. package/src/Processor/HostMemory.js +7 -1
  68. package/src/Processor/IESParser.js +47 -35
  69. package/src/Processor/InstanceTable.js +1 -1
  70. package/src/Processor/KernelManager.js +35 -2
  71. package/src/Processor/MaterialPacking.js +10 -8
  72. package/src/Processor/PBRT/PBRTMaterials.js +254 -42
  73. package/src/Processor/PBRT/PBRTParser.js +49 -19
  74. package/src/Processor/PBRT/PBRTScattering.js +93 -0
  75. package/src/Processor/PBRT/PBRTSceneBuilder.js +1050 -75
  76. package/src/Processor/PBRT/PBRTTextureBake.js +179 -0
  77. package/src/Processor/PBRT/PFM.js +59 -0
  78. package/src/Processor/PackedRayBuffer.js +116 -9
  79. package/src/Processor/ParallelBVHBuilder.js +3 -6
  80. package/src/Processor/PhysicalSky.js +5 -4
  81. package/src/Processor/PlatformImageLoader.js +50 -6
  82. package/src/Processor/QueueManager.js +39 -5
  83. package/src/Processor/ReinsertionOptimizer.js +2 -2
  84. package/src/Processor/SceneMetadata.js +30 -5
  85. package/src/Processor/SceneProcessor.js +53 -62
  86. package/src/Processor/ShaderBuilder.js +6 -16
  87. package/src/Processor/SimpleSky.js +1 -62
  88. package/src/Processor/SolarLimb.js +3 -0
  89. package/src/Processor/SplitBLAS.js +1 -1
  90. package/src/Processor/StorageTexturePool.js +22 -1
  91. package/src/Processor/TLASBuilder.js +1 -3
  92. package/src/Processor/TextureBuckets.js +237 -0
  93. package/src/Processor/TextureCreator.js +181 -8
  94. package/src/Processor/ToneMapGPU.js +64 -15
  95. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  96. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  97. package/src/Processor/Workers/BVHWorker.js +5 -18
  98. package/src/Processor/Workers/CDFWorker.js +6 -183
  99. package/src/Processor/Workers/TexturesWorker.js +103 -18
  100. package/src/Processor/archiveFormats.js +7 -0
  101. package/src/Processor/utils.js +2 -1
  102. package/src/RayzeeRenderer.js +3900 -0
  103. package/src/RenderSettings.js +68 -62
  104. package/src/Stages/ASVGF.js +13 -14
  105. package/src/Stages/AutoExposure.js +17 -17
  106. package/src/Stages/BilateralFilter.js +2 -1
  107. package/src/Stages/Compositor.js +8 -23
  108. package/src/Stages/DenoiserSettings.js +182 -0
  109. package/src/Stages/EdgeFilter.js +2 -1
  110. package/src/Stages/MotionVector.js +1 -1
  111. package/src/Stages/NRD.js +11 -5
  112. package/src/Stages/NormalDepth.js +12 -8
  113. package/src/Stages/PathTracer.js +336 -43
  114. package/src/Stages/PathTracerStage.js +69 -66
  115. package/src/Stages/Variance.js +1 -1
  116. package/src/Storage/BLASCache.js +1 -1
  117. package/src/Storage/CDFCache.js +18 -17
  118. package/src/Storage/DownloadCache.js +1 -1
  119. package/src/Storage/SceneGraphCodec.js +46 -3
  120. package/src/Storage/SpillStore.js +1 -1
  121. package/src/Storage/StorageManager.js +45 -9
  122. package/src/Storage/areas.js +4 -0
  123. package/src/TSL/BVHTraversal.js +50 -25
  124. package/src/TSL/Bidirectional.js +208 -0
  125. package/src/TSL/BidirectionalLamps.js +132 -0
  126. package/src/TSL/Clearcoat.js +15 -6
  127. package/src/TSL/Common.js +20 -15
  128. package/src/TSL/ConnectKernel.js +167 -0
  129. package/src/TSL/Debugger.js +1 -1
  130. package/src/TSL/Displacement.js +4 -3
  131. package/src/TSL/EmissiveSampling.js +65 -18
  132. package/src/TSL/Environment.js +77 -106
  133. package/src/TSL/EnvironmentCDF.js +85 -39
  134. package/src/TSL/ExtendKernel.js +8 -3
  135. package/src/TSL/FinalWriteKernel.js +4 -4
  136. package/src/TSL/GenerateKernel.js +18 -4
  137. package/src/TSL/HitFacet.js +35 -5
  138. package/src/TSL/LightBVHSampling.js +40 -9
  139. package/src/TSL/LightGenerateKernel.js +293 -0
  140. package/src/TSL/LightGuide.js +198 -0
  141. package/src/TSL/LightSplatKernel.js +180 -0
  142. package/src/TSL/LightVisibility.js +111 -0
  143. package/src/TSL/LightsCore.js +13 -38
  144. package/src/TSL/LightsDirect.js +28 -29
  145. package/src/TSL/LightsIndirect.js +3 -1
  146. package/src/TSL/LightsSampling.js +239 -51
  147. package/src/TSL/MaterialEvaluation.js +113 -31
  148. package/src/TSL/MaterialProperties.js +44 -17
  149. package/src/TSL/MaterialTransmission.js +52 -47
  150. package/src/TSL/MergeKernel.js +224 -0
  151. package/src/TSL/PathTracerCore.js +65 -29
  152. package/src/TSL/Random.js +5 -1
  153. package/src/TSL/SceneResources.js +51 -0
  154. package/src/TSL/ShadeKernel.js +985 -101
  155. package/src/TSL/Struct.js +10 -0
  156. package/src/TSL/Sun.js +1 -1
  157. package/src/TSL/TextureSampling.js +66 -61
  158. package/src/addons/archives.js +14 -0
  159. package/src/addons/bidirectional.js +13 -0
  160. package/src/addons/color.js +32 -0
  161. package/src/addons/formats.js +16 -0
  162. package/src/addons/physicalSky.js +14 -0
  163. package/src/addons/storage.js +19 -0
  164. package/src/core.js +31 -0
  165. package/src/index.js +13 -23
  166. package/src/integrators/BidirectionalIntegrator.js +838 -0
  167. package/src/managers/AnimationManager.js +88 -6
  168. package/src/managers/CameraManager.js +103 -46
  169. package/src/managers/DenoisingManager.js +14 -10
  170. package/src/managers/EnvironmentManager.js +125 -103
  171. package/src/managers/GoboManager.js +9 -0
  172. package/src/managers/MaterialDataManager.js +48 -37
  173. package/src/managers/UniformManager.js +32 -10
  174. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  175. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  176. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -7,23 +7,28 @@
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';
14
16
  import { buildGenerateKernel, GENERATE_WG_SIZE } from '../TSL/GenerateKernel.js';
15
17
  import { buildExtendKernel, EXTEND_WG_SIZE } from '../TSL/ExtendKernel.js';
16
18
  import { buildShadeKernel, SHADE_WG_SIZE } from '../TSL/ShadeKernel.js';
19
+ import { buildVisibilityFoldKernel, buildVisibilityClearKernel } from '../TSL/LightVisibility.js';
17
20
  import { buildCompactKernel, buildCompactSubgroupKernel, COMPACT_WG_SIZE } from '../TSL/CompactKernel.js';
18
21
  import { buildFinalWriteKernel, FINALWRITE_WG_SIZE } from '../TSL/FinalWriteKernel.js';
19
22
  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';
23
+ import { buildBucketTextureNodes, refreshBucketTextureNodes } from '../TSL/TextureSampling.js';
24
+ import { withSceneResources, MATERIAL_LAYERS } from '../TSL/SceneResources.js';
22
25
  import {
23
26
  buildResetGlobalHistKernel, buildGlobalHistKernel, buildGlobalPrefixKernel, buildGlobalScatterKernel,
24
27
  SORT_GLOBAL_WG_SIZE, SORT_GLOBAL_MAX_BINS,
25
28
  } from '../TSL/SortGlobalKernels.js';
26
- import { ENGINE_DEFAULTS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
29
+ import { ENGINE_DEFAULTS } from '../EngineDefaults.js';
30
+ import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
31
+ import { ISSUE_CODES } from '../EngineIssues.js';
27
32
  import { createLogger, fmt } from '../utils/Logger.js';
28
33
  import { deviceMemoryGB } from '../Processor/HostMemory.js';
29
34
  import {
@@ -33,11 +38,14 @@ import {
33
38
  const log = createLogger( 'wavefront' );
34
39
 
35
40
  // Shared by the 1D index-list kernels built inline below.
36
- const LIST_WG_SIZE = 256;
41
+ export const LIST_WG_SIZE = 256;
37
42
 
38
43
  // Resized per bounce iteration, each from its own registered workgroup size. Unregistered entries
39
44
  // (the sort passes when _sortMaterials is off) are skipped by setDispatchForCount.
40
- const BOUNCE_KERNELS = [ 'extend', 'shade', 'globalHist', 'globalScatter', 'compact', 'compactCopyback' ];
45
+ const PICKED_SHADOW_RAYS = [ 'two', 'one' ];
46
+ const SHADOW_RAY_MODES = [ 'all', ...PICKED_SHADOW_RAYS ];
47
+
48
+ export const BOUNCE_KERNELS = [ 'extend', 'shade', 'connect', 'merge', 'globalHist', 'globalScatter', 'compact', 'compactCopyback', 'lightCopyback' ];
41
49
 
42
50
  export class PathTracer extends PathTracerStage {
43
51
 
@@ -58,6 +66,9 @@ export class PathTracer extends PathTracerStage {
58
66
  this._convDebugSource = null; // memoized handle set for the Compositor's convergence overlay
59
67
  this._dilateFrozenUniform = uniform( 1, 'int' ); // 1 = dilate (default); 0 = plain per-pixel freeze
60
68
  this._wavefrontReady = false;
69
+ // The background compile of the kernels last built (_compileKernels), and its build's number.
70
+ this._kernelsCompiling = null;
71
+ this._kernelGeneration = 0;
61
72
 
62
73
  // Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. When no denoiser is active the
63
74
  // wavefront skips those writes (Generate/Shade G-buffer + FinalWrite stores). Gated by a live
@@ -77,9 +88,10 @@ export class PathTracer extends PathTracerStage {
77
88
  // CPU sizes per-bounce kernels from last frame's survivor curve; kernels bound on ENTERING_COUNT so over-sizing is safe. (indirect dispatch not viable — three.js doesn't sync compute-written indirect buffers across submissions)
78
89
  this._useDynamicDispatch = true;
79
90
 
80
- // Global material-coherence sort: set per-build from ENGINE_DEFAULTS + material count (>8).
91
+ // Global material-coherence sort: the wavefrontSortMaterials setting, applied per build above 8 materials.
81
92
  // Reorders entering rays into material-pure workgroups before Shade; runs under dynamic dispatch
82
93
  // (compact reads the unsorted active list, so the survivor set is unchanged). Measured −8% at 1024²/8b.
94
+ this.sortMaterials = options.sortMaterials ?? ENGINE_DEFAULTS.wavefrontSortMaterials;
83
95
  this._sortMaterials = false;
84
96
 
85
97
  // Flag-gated off: perf-neutral vs atomic-append and adds a 'subgroups' feature dependency.
@@ -118,6 +130,7 @@ export class PathTracer extends PathTracerStage {
118
130
  // so when one lands cannot change the image. { due, read, apply } while one is in flight.
119
131
  this._lockstep = false;
120
132
  this._lockstepRead = null;
133
+ this._accumulationLockstep = false;
121
134
 
122
135
  // Tier-2: last settled active-pixel count (maxRays − frozen), sizes next frame's bounce-0 grid.
123
136
  // 0 until a settled readback lands (and on reset/camera-move/resize) → grid stays full-size until then.
@@ -141,6 +154,21 @@ export class PathTracer extends PathTracerStage {
141
154
  this._chunkRows = 0; // rows per chunk = floor(B / renderWidth), clamped ≥1
142
155
  this._numChunks = 1; // ceil(renderHeight / chunkRows)
143
156
 
157
+ // Outputs a later stage asked for (requestOutput), compiled into Shade only while asked.
158
+ this._outputs = new Map();
159
+ this._outputsChanged = false;
160
+ // The material layers the kernels were compiled with (_layersToCompile).
161
+ this._compiledLayers = null;
162
+ this._shadowRays = 'all';
163
+ this._compiledPick = null;
164
+ this._learnsVisibility = false;
165
+
166
+ // Integrators other than the path tracer's own, by name (registerIntegrator); the chosen one's instance, or
167
+ // null for plain path tracing, which builds exactly the unidirectional kernels.
168
+ this._integratorFactories = new Map();
169
+ this._integratorInstances = new Map();
170
+ this._integrator = null;
171
+
144
172
  // VRAM accounting — providers are thunks reading CURRENT live resources,
145
173
  // so they survive buffer/texture reallocation (resize, scene/material reload).
146
174
  this.vramTracker = new VRAMTracker( this.renderer );
@@ -158,10 +186,13 @@ export class PathTracer extends PathTracerStage {
158
186
  t.register( 'rays', () => {
159
187
 
160
188
  const a = this._packedBuffers?._attrs;
161
- return a ? [ a.ray, a.rng, a.hit ] : null;
189
+ return a ? [ a.ray, a.hit ] : null;
162
190
 
163
191
  } );
164
192
 
193
+ // An integrator's own buffers (a light vertex cache rides in the hit buffer, under 'rays')
194
+ t.register( 'integrator', () => this._integrator?.gpuResources() ?? null );
195
+
165
196
  // Queue indices + atomic counters
166
197
  t.register( 'queues', () => {
167
198
 
@@ -235,14 +266,152 @@ export class PathTracer extends PathTracerStage {
235
266
 
236
267
  }
237
268
 
269
+ /**
270
+ * Asks for an extra per-pixel output, compiled in only while someone asks for it; the kernels rebuild before the
271
+ * next frame. `'gBuffer'`: the denoisers' normal/depth (`pathtracer:normalDepth`) and albedo (`pathtracer:albedo`),
272
+ * written while setAuxGBufferEnabled( true ). `'hitDistance'`: the first bounce's segment plus any alpha-skip run,
273
+ * at camera depth 1, as `encode( distance, viewZ )` returns it — a [0, 1] value, in `pathtracer:albedo.w`; it
274
+ * brings the G-buffer with it.
275
+ * @param {'gBuffer'|'hitDistance'} name
276
+ * @param {{encode?: function(Node, Node): Node}} [options]
277
+ * @returns {function(): void} withdraws the request
278
+ */
279
+ requestOutput( name, options = {} ) {
280
+
281
+ this._outputs.set( name, options );
282
+ this._outputsChanged = true;
283
+
284
+ return () => {
285
+
286
+ if ( this._outputs.get( name ) !== options ) return;
287
+ this._outputs.delete( name );
288
+ this._outputsChanged = true;
289
+
290
+ };
291
+
292
+ }
293
+
294
+ /**
295
+ * Makes an integrator choosable by name. `create( pathTracer )` makes its instance, once, which then takes the
296
+ * integrator hooks this class calls (see BidirectionalIntegrator); one class may answer several names.
297
+ * @param {string|string[]} names
298
+ * @param {function(PathTracer): Object} create
299
+ */
300
+ registerIntegrator( names, create ) {
301
+
302
+ for ( const name of [ names ].flat() ) this._integratorFactories.set( name, create );
303
+
304
+ }
305
+
306
+ /**
307
+ * Switching rebuilds the kernels: only an integrator's own bind its buffers. 'path' is the path tracer's own;
308
+ * the others must have been registered (rayzee/addons/bidirectional: 'bidirectional', 'vcm').
309
+ * @param {string} name
310
+ */
311
+ setIntegrator( name ) {
312
+
313
+ let next = null;
314
+ if ( name !== 'path' ) {
315
+
316
+ const create = this._integratorFactories.get( name );
317
+ if ( ! create ) {
318
+
319
+ this.issues?.record( ISSUE_CODES.CAPABILITY_MISSING, `integrator "${name}" is not installed: pathTracer.registerIntegrator(), or rayzee/addons/bidirectional`, { capability: name } );
320
+ return;
321
+
322
+ }
323
+
324
+ next = this._integratorInstances.get( create );
325
+ if ( ! next ) this._integratorInstances.set( create, next = create( this ) );
326
+
327
+ }
328
+
329
+ if ( next === this._integrator && ( ! next || next.name === name ) ) return;
330
+ if ( this._integrator && this._integrator !== next ) this._integrator.dispose();
331
+ next?.select( name );
332
+ this._integrator = next;
333
+ this._pathBudget = 0;
334
+
335
+ if ( this._packedBuffers && this.materialData?.materialCount > 0 ) {
336
+
337
+ if ( this._kernelManager ) this._kernelManager.dispose();
338
+ this._wavefrontReady = false;
339
+ this._buildWavefrontKernels();
340
+
341
+ }
342
+
343
+ }
344
+
345
+ /**
346
+ * Shadow rays a hit for the lamps, sky, sun and emitters: 'all', one per kind; 'two', the strongest and one picked
347
+ * from the rest; 'one', one picked, by unshadowed light and where each kind gets through. Rebuilds the kernels
348
+ * before the next frame; bidirectional integrators ignore it.
349
+ * @param {'all'|'two'|'one'} mode
350
+ */
351
+ setShadowRays( mode ) {
352
+
353
+ if ( ! SHADOW_RAY_MODES.includes( mode ) ) {
354
+
355
+ log.warn( `shadowRays: unknown mode "${mode}", using 'all'` );
356
+ mode = 'all';
357
+
358
+ }
359
+
360
+ if ( this._shadowRays === mode ) return;
361
+ this._shadowRays = mode;
362
+ this._outputsChanged = true;
363
+
364
+ }
365
+
366
+ /**
367
+ * The lightPick variant to compile, or null for a ray per light kind: also when the scene has no more kinds than the
368
+ * variant traces rays, where it would only add its bookkeeping.
369
+ */
370
+ _pickedShadowRays() {
371
+
372
+ if ( this._integrator || ! PICKED_SHADOW_RAYS.includes( this._shadowRays ) ) return null;
373
+ const environment = this.enableEnvironment.value > 0;
374
+ const lamps = this.numDirectionalLights.value + this.numAreaLights.value + this.numPointLights.value + this.numSpotLights.value;
375
+ const kinds = Number( lamps > 0 )
376
+ + Number( environment && this.envTotalSum.value > 0 )
377
+ + Number( environment && this.hasSun.value > 0 )
378
+ + Number( this.enableEmissiveTriangleSampling.value > 0 && this.emissiveTriangleCount.value > 0 );
379
+ return kinds > ( this._shadowRays === 'two' ? 2 : 1 ) ? this._shadowRays : null;
380
+
381
+ }
382
+
383
+ get integrator() {
384
+
385
+ return this._integrator?.name ?? 'path';
386
+
387
+ }
388
+
389
+ /** The chosen integrator's instance — its controls, e.g. setStrategy() — or null for plain path tracing. */
390
+ get activeIntegrator() {
391
+
392
+ return this._integrator;
393
+
394
+ }
395
+
238
396
  render( context ) {
239
397
 
240
398
  // Kernels not built yet (first frame / mid-resize) — skip until ready.
241
399
  if ( ! this.isReady || ! this._wavefrontReady ) return;
242
400
 
243
- // The packed light buffer was grow-reallocated at runtime (emissive set grew) —
244
- // the compiled kernels still bind the old attribute, so rebuild before rendering.
245
- if ( this._lightBufferRealloc ) {
401
+ // The packed light buffer was grow-reallocated at runtime (emissive set grew) or a light list grew —
402
+ // the compiled kernels still bind the old one, so rebuild before rendering. Likewise a changed output request.
403
+ // A layer some material now uses is compiled in at once; one nothing uses any more waits for the next build.
404
+ if ( this._materialFeaturesChanged ) {
405
+
406
+ this._materialFeaturesChanged = false;
407
+ const needed = this._layersToCompile();
408
+ if ( MATERIAL_LAYERS.some( ( layer ) => needed[ layer ] && ! this._compiledLayers?.[ layer ] ) ) this._outputsChanged = true;
409
+
410
+ }
411
+
412
+ if ( PICKED_SHADOW_RAYS.includes( this._shadowRays ) && this._pickedShadowRays() !== this._compiledPick ) this._outputsChanged = true;
413
+
414
+ if ( this._lightBufferRealloc || this._outputsChanged ) {
246
415
 
247
416
  if ( this._kernelManager ) this._kernelManager.dispose();
248
417
  this._wavefrontReady = false;
@@ -291,7 +460,7 @@ export class PathTracer extends PathTracerStage {
291
460
  }
292
461
 
293
462
  this._handleResize();
294
- this.manageASVGFForRenderMode( renderMode );
463
+ this.resetHistoryOnRenderModeChange( renderMode );
295
464
 
296
465
  // Full-frame render is always a complete cycle (PER_CYCLE stages gate on this).
297
466
  if ( context ) context.setState( 'tileRenderingComplete', true );
@@ -320,6 +489,7 @@ export class PathTracer extends PathTracerStage {
320
489
  this.frame.value = frameValue;
321
490
  this.seedFrame.value = this._seedTick ++;
322
491
  this.tracedFrames ++;
492
+ if ( ! this._lockstep ) this._accumulationLockstep = false;
323
493
 
324
494
  this._setWfDispatch();
325
495
 
@@ -388,6 +558,15 @@ export class PathTracer extends PathTracerStage {
388
558
  : Math.min( this._lastBounceCountsBudget, this._lastBounceCountsLoopBound ) )
389
559
  : 0;
390
560
 
561
+ // An integrator's light subpaths first, through the same pool; every camera chunk then connects to them.
562
+ const integrator = this._integrator?.beginFrame( loopBound ) ? this._integrator : null;
563
+ // A camera move keeps what was learned (it is kept per place in the scene); a repeatable render starts afresh.
564
+ if ( this._learnsVisibility && frameValue === 0 && ( this._pinSeedToFrame || this._lockstep || ! this.cameraChanged ) ) {
565
+
566
+ km.dispatch( 'visibilityClear' );
567
+
568
+ }
569
+
391
570
  // Blender-style row-band streaming: the fixed-budget path pool processes the image in bands of ≤ chunkRows
392
571
  // rows (one chunk when the frame fits the budget → identical to the pre-chunking path). See spec.
393
572
  for ( let chunkIndex = 0, rowBase = 0; rowBase < frameHeight; rowBase += chunkRows, chunkIndex ++ ) {
@@ -490,7 +669,9 @@ export class PathTracer extends PathTracerStage {
490
669
 
491
670
  }
492
671
 
672
+ integrator?.beforeShade();
493
673
  km.dispatch( 'shade' ); // shade thread 0 folds resetActiveCounter (zeroes ACTIVE_RAY_COUNT before compact)
674
+ integrator?.afterShade( km );
494
675
  km.dispatch( 'compact' );
495
676
  if ( useFunctionalCompaction ) {
496
677
 
@@ -519,10 +700,12 @@ export class PathTracer extends PathTracerStage {
519
700
 
520
701
  }
521
702
 
703
+ integrator?.resolve( km );
522
704
  km.dispatch( 'finalWrite' );
523
705
 
524
706
  }
525
707
 
708
+ if ( this._learnsVisibility ) km.dispatch( 'visibilityFold' );
526
709
  this._maybeReadbackCounters();
527
710
 
528
711
  // Skip the normalDepth/albedo copies when aux is off — the wavefront didn't write them and
@@ -661,6 +844,8 @@ export class PathTracer extends PathTracerStage {
661
844
  this._convergedFraction = state.convergedFraction ?? 0;
662
845
  this._convergedGeometryFraction = state.convergedGeometryFraction ?? 0;
663
846
  this.isComplete = false;
847
+ // The survivor curve is not in a checkpoint, so a continuation is not the lockstep render.
848
+ this._accumulationLockstep = false;
664
849
 
665
850
  }
666
851
 
@@ -711,18 +896,58 @@ export class PathTracer extends PathTracerStage {
711
896
 
712
897
  }
713
898
 
899
+ /** Whether the current image was traced in lockstep from a reset made in it; with no sample yet, the setting. */
900
+ get accumulationLockstep() {
901
+
902
+ return this.frameCount > 0 ? this._accumulationLockstep : this._lockstep;
903
+
904
+ }
905
+
714
906
  /**
715
- * While a lockstep readback is due and has not landed, render() traces nothing: await this
716
- * before the next frame. Null otherwise.
907
+ * While the kernels compile in the background, or a lockstep readback is due and has not landed, render() traces
908
+ * nothing: await this before the next frame. Null otherwise.
717
909
  * @returns {?Promise<void>}
718
910
  */
719
911
  readbackWait() {
720
912
 
913
+ if ( this._kernelsCompiling ) return this._kernelsCompiling;
721
914
  const pending = this._lockstepRead;
722
915
  return pending && ! pending.apply && this.frameCount >= pending.due ? pending.read : null;
723
916
 
724
917
  }
725
918
 
919
+ /**
920
+ * Compiles the kernels just built without blocking the page. Until they are ready render() traces nothing and the
921
+ * canvas keeps its last frame; every driver loop awaits readbackWait(), which returns this. A build started
922
+ * meanwhile supersedes it. A failed background compile — and every compile in Node — leaves each kernel to compile
923
+ * at its first dispatch.
924
+ */
925
+ _compileKernels() {
926
+
927
+ const generation = ++ this._kernelGeneration;
928
+ if ( ! KernelManager.canCompileInBackground ) {
929
+
930
+ this._kernelsCompiling = null;
931
+ this._wavefrontReady = true;
932
+ return;
933
+
934
+ }
935
+
936
+ const kernels = this._kernelManager;
937
+ this.emit( 'pathtracer:compiling', { compiling: true } );
938
+ this._kernelsCompiling = kernels.compile()
939
+ .catch( ( error ) => log.warn( `background kernel compile failed — compiling at first dispatch: ${error?.message ?? error}` ) )
940
+ .then( () => {
941
+
942
+ if ( generation !== this._kernelGeneration ) return;
943
+ this._kernelsCompiling = null;
944
+ this._wavefrontReady = true;
945
+ this.emit( 'pathtracer:compiling', { compiling: false } );
946
+
947
+ } );
948
+
949
+ }
950
+
726
951
  /**
727
952
  * Per-pixel convergence state for the Compositor's debug overlay: the buffers FinalWrite maintains
728
953
  * plus the uniforms needed to re-derive its predicate. Read-only — the overlay never writes back.
@@ -787,11 +1012,12 @@ export class PathTracer extends PathTracerStage {
787
1012
  }
788
1013
 
789
1014
  // Aux MRT (normalDepth/albedo) is needed only by the denoiser/OIDN; DenoisingManager calls this to
790
- // turn the wavefront's aux writes on/off. It's a live uniform, so toggling is just a value flip —
791
- // no kernel rebuild, no UI freeze.
1015
+ // turn the wavefront's aux writes on/off. The first call that turns them on compiles them in (requestOutput,
1016
+ // kept for good); after that it is a live uniform, so toggling is a value flip — no kernel rebuild, no UI freeze.
792
1017
  setAuxGBufferEnabled( enabled ) {
793
1018
 
794
1019
  enabled = !! enabled;
1020
+ if ( enabled && ! this._outputs.has( 'gBuffer' ) ) this.requestOutput( 'gBuffer' );
795
1021
  if ( this._auxGBufferEnabled === enabled ) return;
796
1022
  this._auxGBufferEnabled = enabled;
797
1023
  this._auxGBufferUniform.value = enabled ? 1 : 0;
@@ -877,7 +1103,8 @@ export class PathTracer extends PathTracerStage {
877
1103
 
878
1104
  _bounceLoopBound() {
879
1105
 
880
- return this.maxBounces.value + this.transmissiveBounces.value + this.maxSubsurfaceSteps.value;
1106
+ // Camera paths take one segment past their last bounce (ShadeKernel, EMISSION_ONLY).
1107
+ return this.maxBounces.value + this.transmissiveBounces.value + this.maxSubsurfaceSteps.value + 1;
881
1108
 
882
1109
  }
883
1110
 
@@ -931,12 +1158,13 @@ export class PathTracer extends PathTracerStage {
931
1158
  const budget = this.maxBounces.value;
932
1159
  const measuredLoopBound = this._bounceLoopBound();
933
1160
  const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
1161
+ const lightKey = this._integrator?.curveKey ?? null;
934
1162
  this.renderer.getArrayBufferAsync( attr ).then( ( buf ) => {
935
1163
 
936
1164
  // Drop counts measured at a now-stale generation (a resize or camera move happened mid-flight).
937
1165
  // A surviving readback was initiated while settled (init is skipped mid-motion) and no motion
938
1166
  // happened before it resolved, so its counts match the current view — safe to size from.
939
- if ( gen === this._readbackGeneration ) this._applyBounceCounts( buf, budget, measuredLoopBound, n );
1167
+ if ( gen === this._readbackGeneration ) this._applyBounceCounts( buf, budget, measuredLoopBound, n, lightKey );
940
1168
 
941
1169
  this._readbackPending = false;
942
1170
 
@@ -960,7 +1188,7 @@ export class PathTracer extends PathTracerStage {
960
1188
  // Full-FRAME pixel count (CONVERGED_COUNT sums across all chunks) — not _wfMaxRayCount, which
961
1189
  // after the chunk loop holds only the last band's pixel count.
962
1190
  const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
963
- this.renderer.getArrayBufferAsync( cAttr ).then( ( buf ) => {
1191
+ this.renderer.getArrayBufferAsync( cAttr, null, 0, COUNTER.COUNT * 4 ).then( ( buf ) => {
964
1192
 
965
1193
  if ( cgen === this._readbackGeneration && total > 0 ) this._applyCounters( buf, total );
966
1194
 
@@ -993,17 +1221,18 @@ export class PathTracer extends PathTracerStage {
993
1221
  const loopBound = this._bounceLoopBound();
994
1222
  const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
995
1223
  const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
1224
+ const lightKey = this._integrator?.curveKey ?? null;
996
1225
 
997
1226
  const entry = { due: this.frameCount + this._readbackEveryNFrames, read: null, apply: null };
998
1227
  entry.read = Promise.all( [
999
1228
  this.renderer.getArrayBufferAsync( bounceAttr ),
1000
- this.renderer.getArrayBufferAsync( counterAttr ),
1229
+ this.renderer.getArrayBufferAsync( counterAttr, null, 0, COUNTER.COUNT * 4 ),
1001
1230
  ] ).then( ( [ bounceBuf, counterBuf ] ) => {
1002
1231
 
1003
1232
  entry.apply = () => {
1004
1233
 
1005
1234
  if ( gen !== this._readbackGeneration ) return;
1006
- this._applyBounceCounts( bounceBuf, budget, loopBound, n );
1235
+ this._applyBounceCounts( bounceBuf, budget, loopBound, n, lightKey );
1007
1236
  if ( total > 0 ) this._applyCounters( counterBuf, total );
1008
1237
 
1009
1238
  };
@@ -1019,11 +1248,13 @@ export class PathTracer extends PathTracerStage {
1019
1248
 
1020
1249
  }
1021
1250
 
1022
- _applyBounceCounts( buf, budget, loopBound, n ) {
1251
+ _applyBounceCounts( buf, budget, loopBound, n, lightKey = null ) {
1023
1252
 
1024
1253
  const all = new Uint32Array( buf.slice( 0 ) );
1025
1254
  this._lastBounceCounts = all.subarray( 0, n );
1026
1255
  this._lastBounceEnergy = all.subarray( n, 2 * n );
1256
+ // An integrator's light pass keeps its own curve after the camera's.
1257
+ this._integrator?.applyLightCurve( all.subarray( 2 * n, 3 * n ), all.subarray( 3 * n, 4 * n ), lightKey );
1027
1258
  this._lastBounceCountsBudget = budget;
1028
1259
  this._lastBounceCountsLoopBound = loopBound;
1029
1260
  this._curveSizingValid = true;
@@ -1137,19 +1368,23 @@ export class PathTracer extends PathTracerStage {
1137
1368
  // any resolution through this pool, so a small B just means more row-bands (works on weak / small-VRAM GPUs).
1138
1369
  _computePathBudget() {
1139
1370
 
1140
- if ( this._pathBudgetOverride > 0 ) return this._pathBudgetOverride;
1141
-
1142
1371
  const RAY_BYTES = RAY_STRIDE * 16;
1143
- const bytesPerPath = RAY_STRIDE * 16 + HIT_STRIDE * 16 + 4 /* rng */ + GBUFFER_STRIDE * 16
1372
+ const HIT_BYTES = ( this._integrator?.hitStride ?? HIT_STRIDE ) * 16;
1373
+ const bytesPerPath = RAY_BYTES + HIT_BYTES + GBUFFER_STRIDE * 16
1144
1374
  + 4 + 4 /* activeIndices A/B */ + 4;
1145
1375
 
1146
1376
  const limits = this.renderer?.backend?.device?.limits;
1147
1377
  const maxBinding = limits?.maxStorageBufferBindingSize || ( 128 * 1024 * 1024 );
1148
1378
  const maxBuffer = limits?.maxBufferSize || maxBinding;
1149
-
1150
- const bByBinding = Math.floor( ( maxBinding * 0.9 ) / RAY_BYTES );
1151
1379
  const deviceMemBytes = deviceMemoryGB( this.hostMemoryGB ).gb * 1024 * 1024 * 1024;
1152
- const poolVramBudget = Math.min( deviceMemBytes * 0.25, maxBuffer * 4 );
1380
+
1381
+ // An integrator's light vertex cache shares the hit buffer's binding with the paths.
1382
+ const cacheBytes = this._integrator?.cacheBytes( maxBinding, deviceMemBytes ) ?? 0;
1383
+
1384
+ if ( this._pathBudgetOverride > 0 ) return this._pathBudgetOverride;
1385
+
1386
+ const bByBinding = Math.floor( Math.min( ( maxBinding * 0.9 ) / RAY_BYTES, ( maxBinding * 0.9 - cacheBytes ) / HIT_BYTES ) );
1387
+ const poolVramBudget = Math.min( deviceMemBytes * 0.25, maxBuffer * 4 ) - cacheBytes;
1153
1388
  const bByVram = Math.floor( poolVramBudget / bytesPerPath );
1154
1389
 
1155
1390
  const MIN_B = 512 * 512;
@@ -1208,6 +1443,20 @@ export class PathTracer extends PathTracerStage {
1208
1443
 
1209
1444
  }
1210
1445
 
1446
+ /** The material layers the scene uses. */
1447
+ _layersToCompile() {
1448
+
1449
+ return this.materialData.materialLayers();
1450
+
1451
+ }
1452
+
1453
+ /** Whether the kernels write the G-buffer: asked for directly, or for the hit distance that rides in it. */
1454
+ get _gBufferCompiled() {
1455
+
1456
+ return this._outputs.has( 'gBuffer' ) || this._outputs.has( 'hitDistance' );
1457
+
1458
+ }
1459
+
1211
1460
  _buildWavefrontKernels() {
1212
1461
 
1213
1462
  const texNodes = this.shaderBuilder.getSceneTextureNodes();
@@ -1215,6 +1464,8 @@ export class PathTracer extends PathTracerStage {
1215
1464
 
1216
1465
  // A fresh build binds the current lightStorageAttr — any pending realloc is covered.
1217
1466
  this._lightBufferRealloc = false;
1467
+ this._outputsChanged = false;
1468
+ this._integrator?.beforeKernelBuild();
1218
1469
 
1219
1470
  const w = this.storageTextures.renderWidth;
1220
1471
  const h = this.storageTextures.renderHeight;
@@ -1231,13 +1482,14 @@ export class PathTracer extends PathTracerStage {
1231
1482
 
1232
1483
  // Per-path buffers (RAY/HIT/rng) sized to the budget B and indexed by LOCAL slot r ∈ [0,B). _cap = B is
1233
1484
  // baked into the SoA stride but never changes with resolution, so this build happens once (model load).
1485
+ const lightVertices = this._integrator?.lightVertices ?? 0;
1234
1486
  if ( ! this._packedBuffers ) {
1235
1487
 
1236
- this._packedBuffers = new PackedRayBuffer( B, this.renderer );
1488
+ this._packedBuffers = new PackedRayBuffer( B, this.renderer, lightVertices );
1237
1489
 
1238
1490
  } else {
1239
1491
 
1240
- this._packedBuffers.resize( B );
1492
+ this._packedBuffers.resize( B, lightVertices );
1241
1493
 
1242
1494
  }
1243
1495
 
@@ -1275,6 +1527,8 @@ export class PathTracer extends PathTracerStage {
1275
1527
 
1276
1528
  }
1277
1529
 
1530
+ this._integrator?.allocate( maxPixels );
1531
+
1278
1532
  const m2RW = storage( this._m2Attr, 'float' );
1279
1533
  const streakRW = storage( this._streakAttr, 'uint' );
1280
1534
  const streakRO = storage( this._streakAttr, 'uint' ).toReadOnly();
@@ -1310,6 +1564,11 @@ export class PathTracer extends PathTracerStage {
1310
1564
  const prevNormalDepth = this.shaderBuilder.prevNormalDepthTexNode;
1311
1565
  const writeTex = this.storageTextures.getWriteTextures();
1312
1566
 
1567
+ const picked = this._pickedShadowRays();
1568
+ this._compiledPick = picked;
1569
+ const learnsVisibility = picked !== null;
1570
+ this._learnsVisibility = learnsVisibility;
1571
+ qm.setVisibilityTable( learnsVisibility );
1313
1572
  const counters = qm.getCounters();
1314
1573
 
1315
1574
  // Copy ACTIVE_RAY_COUNT into bounceCounts[currentBounce] for the readback survivor curve.
@@ -1393,6 +1652,8 @@ export class PathTracer extends PathTracerStage {
1393
1652
  transmissiveBounces: this.transmissiveBounces,
1394
1653
  transparentBackground: this.transparentBackground,
1395
1654
  auxGBufferEnabled: this._auxGBufferUniform,
1655
+ gBuffer: this._gBufferCompiled,
1656
+ bidirectional: this._integrator?.uniforms ?? null,
1396
1657
  };
1397
1658
  const genFn = buildGenerateKernel( genParams );
1398
1659
  this._kernelManager.register( 'generate',
@@ -1586,13 +1847,21 @@ export class PathTracer extends PathTracerStage {
1586
1847
  const _mat = this.materialData;
1587
1848
  const _env = this.environment;
1588
1849
  // Consolidated size-bucket nodes (K sRGB + K linear). Empty buckets get placeholders so
1589
- // every runtime branch references a valid node. Published to the sampling module before
1590
- // the Shade/Debug graphs are built so they bake in these (per-pipeline) nodes.
1850
+ // every runtime branch references a valid node.
1591
1851
  const freshSrgbBuckets = buildBucketTextureNodes( _mat.srgbBuckets );
1592
1852
  const freshLinearBuckets = buildBucketTextureNodes( _mat.linearBuckets );
1593
- setMaterialBucketTextures( freshSrgbBuckets, freshLinearBuckets );
1594
- // Alpha-cutout shadow rays sample albedo (sRGB pool) — emitted into the shade graph now.
1595
- setShadowAlbedoMaps( freshSrgbBuckets );
1853
+ // This renderer's resources, in the build context of each kernel that samples them (SceneResources.js).
1854
+ // Alpha-cutout shadow rays sample albedo (sRGB pool).
1855
+ const resources = {
1856
+ srgbBuckets: freshSrgbBuckets,
1857
+ linearBuckets: freshLinearBuckets,
1858
+ shadowAlbedoMaps: freshSrgbBuckets,
1859
+ goboMaps: texNodes.goboMapsTex,
1860
+ iesProfiles: texNodes.iesProfilesTex,
1861
+ alphaShadows: this.uniforms.get( 'enableAlphaShadows' ),
1862
+ materialLayers: this._compiledLayers = this._layersToCompile(),
1863
+ };
1864
+ const own = ( call ) => withSceneResources( call, resources );
1596
1865
  const freshEnvTex = _env.environmentTexture ? texture( _env.environmentTexture ) : texNodes.envTex;
1597
1866
 
1598
1867
  this._wfTexNodes = {
@@ -1603,8 +1872,7 @@ export class PathTracer extends PathTracerStage {
1603
1872
  };
1604
1873
 
1605
1874
  // Material-coherence sort gate (experiment): only worthwhile above a few materials.
1606
- this._sortMaterials = ( ENGINE_DEFAULTS.wavefrontSortMaterials ?? false )
1607
- && ( this.materialData?.materialCount ?? 0 ) > 8;
1875
+ this._sortMaterials = this.sortMaterials && ( this.materialData?.materialCount ?? 0 ) > 8;
1608
1876
 
1609
1877
  const extFn = buildExtendKernel( {
1610
1878
  bvhBuffer: freshBvh,
@@ -1669,7 +1937,6 @@ export class PathTracer extends PathTracerStage {
1669
1937
 
1670
1938
  const shadeFn = buildShadeKernel( {
1671
1939
  gBufferRW,
1672
- envCompensationDelta: this.envCompensationDelta,
1673
1940
  bvhBuffer: freshBvh,
1674
1941
  triangleBuffer: freshTri,
1675
1942
  materialBuffer: freshMat,
@@ -1721,7 +1988,6 @@ export class PathTracer extends PathTracerStage {
1721
1988
  emissiveVec4Offset: this.emissiveVec4Offset,
1722
1989
  emissiveTotalPower: this.emissiveTotalPower,
1723
1990
  emissiveBoost: this.emissiveBoost,
1724
- totalTriangleCount: this.totalTriangleCount,
1725
1991
  enableEmissiveTriangleSampling: this.enableEmissiveTriangleSampling,
1726
1992
  lightBVHNodeCount: this.lightBVHNodeCount,
1727
1993
  reverseMapVec4Offset: this.reverseMapVec4Offset,
@@ -1729,17 +1995,29 @@ export class PathTracer extends PathTracerStage {
1729
1995
  maxRayCount: this._wfMaxRayCount,
1730
1996
  chunkRowBase: this._wfChunkRowBase,
1731
1997
  auxGBufferEnabled: this._auxGBufferUniform,
1998
+ gBuffer: this._gBufferCompiled,
1732
1999
  hasSun: this.hasSun,
1733
2000
  sunDirection: this.sunDirection,
1734
2001
  sunRadiance: this.sunRadiance,
1735
2002
  sunParams: this.sunParams,
2003
+ bidirectional: this._integrator?.uniforms ?? null,
2004
+ hitDistanceEncode: this._outputs.get( 'hitDistance' )?.encode ?? null,
2005
+ materialLayers: this._compiledLayers,
2006
+ shadowRays: picked,
2007
+ cameraWorldMatrix: this.cameraWorldMatrix,
1736
2008
  } );
1737
2009
  this._kernelManager.register( 'shade',
1738
- shadeFn().compute(
2010
+ own( shadeFn() ).compute(
1739
2011
  [ Math.ceil( maxRays / SHADE_WG_SIZE ), 1, 1 ],
1740
2012
  [ SHADE_WG_SIZE, 1, 1 ]
1741
2013
  )
1742
2014
  );
2015
+ if ( learnsVisibility ) {
2016
+
2017
+ this._kernelManager.register( 'visibilityClear', buildVisibilityClearKernel( { counters } ) );
2018
+ this._kernelManager.register( 'visibilityFold', buildVisibilityFoldKernel( { counters } ) );
2019
+
2020
+ }
1743
2021
 
1744
2022
  // Subgroup prefix-sum variant when supported.
1745
2023
  const subgroupsOK = this._useSubgroupCompact
@@ -1801,6 +2079,11 @@ export class PathTracer extends PathTracerStage {
1801
2079
  copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
1802
2080
  );
1803
2081
 
2082
+ this._integrator?.registerKernels( {
2083
+ km: this._kernelManager, qm, pb, counters, bounceCountsBuf, wfCurrentBounce, copyReadB, copyWriteA, maxRays, w, own,
2084
+ freshLight, freshTri, freshBvh, freshMat, freshEnvTex, freshEnvCDF, materialLayers: this._compiledLayers,
2085
+ } );
2086
+
1804
2087
  const fwFn = buildFinalWriteKernel( {
1805
2088
  rayBufferRO: pb.rayBuffer.ro,
1806
2089
  gBufferRO,
@@ -1823,6 +2106,7 @@ export class PathTracer extends PathTracerStage {
1823
2106
  renderHeight: this._wfRenderHeight,
1824
2107
  visMode: this.visMode,
1825
2108
  auxGBufferEnabled: this._auxGBufferUniform,
2109
+ gBuffer: this._gBufferCompiled,
1826
2110
  cleanAuxNormalEnabled: this._cleanAuxNormalUniform,
1827
2111
  m2BufferRW: m2RW,
1828
2112
  useAdaptiveSampling: this.useAdaptiveSampling,
@@ -1882,14 +2166,16 @@ export class PathTracer extends PathTracerStage {
1882
2166
  debugVisScale: this.debugVisScale,
1883
2167
  frame: this.frame,
1884
2168
  } );
2169
+ // Only the debug views dispatch it, so it compiles when one is first chosen.
1885
2170
  this._kernelManager.register( 'debug',
1886
- debugFn().compute(
2171
+ own( debugFn() ).compute(
1887
2172
  [ Math.ceil( w / DEBUG_WG_SIZE ), Math.ceil( h / DEBUG_WG_SIZE ), 1 ],
1888
2173
  [ DEBUG_WG_SIZE, DEBUG_WG_SIZE, 1 ]
1889
- )
2174
+ ),
2175
+ { eager: false },
1890
2176
  );
1891
2177
 
1892
- this._wavefrontReady = true;
2178
+ this._compileKernels();
1893
2179
 
1894
2180
  const bufferBytes = ( this._packedBuffers?.totalBytes ?? 0 ) + ( this._queueManager?.totalBytes ?? 0 );
1895
2181
 
@@ -1898,6 +2184,7 @@ export class PathTracer extends PathTracerStage {
1898
2184
  `${fmt.mb( bufferBytes )} wavefront buffers`,
1899
2185
  `budget ${fmt.n( B )} paths`,
1900
2186
  this._numChunks > 1 ? `${this._numChunks} chunks of ≤${this._chunkRows} rows` : null,
2187
+ this._integrator?.describe() ?? null,
1901
2188
  ] ) );
1902
2189
 
1903
2190
  }
@@ -1930,6 +2217,7 @@ export class PathTracer extends PathTracerStage {
1930
2217
  const km = this._kernelManager;
1931
2218
  km.setDispatchForGrid( 'generate', w, rows );
1932
2219
  km.setDispatchForGrid( 'finalWrite', w, rows );
2220
+ km.setDispatchForGrid( 'splatResolve', w, rows );
1933
2221
  km.setDispatchForCount( 'initActiveIndices', chunkPixels );
1934
2222
  km.setDispatchForCount( 'buildActivePixels', chunkPixels );
1935
2223
 
@@ -1947,6 +2235,9 @@ export class PathTracer extends PathTracerStage {
1947
2235
  this._m2Attr?.dispose?.();
1948
2236
  this._streakAttr?.dispose?.();
1949
2237
  this._frozenMaskAttr?.dispose?.();
2238
+ for ( const integrator of this._integratorInstances.values() ) integrator.dispose();
2239
+ this._integratorInstances.clear();
2240
+ this._integrator = null;
1950
2241
  this._packedBuffers = null;
1951
2242
  this._queueManager = null;
1952
2243
  this._kernelManager = null;
@@ -1956,6 +2247,8 @@ export class PathTracer extends PathTracerStage {
1956
2247
  this._frozenMaskAttr = null;
1957
2248
  this._convDebugSource = null;
1958
2249
  this._wavefrontReady = false;
2250
+ this._kernelGeneration ++;
2251
+ this._kernelsCompiling = null;
1959
2252
 
1960
2253
  }
1961
2254