rayzee 10.0.0 → 11.0.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 (173) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -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-BCXylThH.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
  14. package/dist/PathTracer-CQPW06Mm.js +29971 -0
  15. package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
  16. package/dist/RayzeeRenderer-COpwotpU.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-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 +2551 -44024
  33. package/dist/rayzee.umd.js +528 -487
  34. package/dist/sceneCachePolicy-BSSsKY98.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 +1 -1
  52. package/src/Processor/ArchiveImporter.js +1259 -0
  53. package/src/Processor/AssetLoader.js +327 -1763
  54. package/src/Processor/AtmosphereModel.js +1 -2
  55. package/src/Processor/BVHBuilder.js +14 -126
  56. package/src/Processor/BVHLeafFold.js +4 -6
  57. package/src/Processor/BVHRefitter.js +67 -46
  58. package/src/Processor/BufferLayout.js +291 -0
  59. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  60. package/src/Processor/EnvironmentExactTable.js +187 -0
  61. package/src/Processor/EquirectHDRInfo.js +30 -223
  62. package/src/Processor/FileFormats.js +116 -0
  63. package/src/Processor/GLTFDecoders.js +85 -0
  64. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  65. package/src/Processor/GeometryExtractor.js +15 -5
  66. package/src/Processor/HostMemory.js +7 -1
  67. package/src/Processor/IESParser.js +47 -35
  68. package/src/Processor/InstanceTable.js +1 -1
  69. package/src/Processor/KernelManager.js +35 -2
  70. package/src/Processor/MaterialPacking.js +10 -8
  71. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  72. package/src/Processor/PBRT/PBRTParser.js +30 -17
  73. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  74. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  75. package/src/Processor/PBRT/PFM.js +59 -0
  76. package/src/Processor/PackedRayBuffer.js +116 -9
  77. package/src/Processor/ParallelBVHBuilder.js +3 -6
  78. package/src/Processor/PhysicalSky.js +5 -4
  79. package/src/Processor/PlatformImageLoader.js +50 -6
  80. package/src/Processor/QueueManager.js +39 -5
  81. package/src/Processor/ReinsertionOptimizer.js +2 -2
  82. package/src/Processor/SceneProcessor.js +53 -62
  83. package/src/Processor/ShaderBuilder.js +6 -16
  84. package/src/Processor/SimpleSky.js +1 -62
  85. package/src/Processor/SolarLimb.js +3 -0
  86. package/src/Processor/SplitBLAS.js +1 -1
  87. package/src/Processor/StorageTexturePool.js +22 -1
  88. package/src/Processor/TLASBuilder.js +1 -3
  89. package/src/Processor/TextureBuckets.js +237 -0
  90. package/src/Processor/TextureCreator.js +181 -8
  91. package/src/Processor/ToneMapGPU.js +64 -15
  92. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  93. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  94. package/src/Processor/Workers/BVHWorker.js +5 -18
  95. package/src/Processor/Workers/CDFWorker.js +6 -183
  96. package/src/Processor/Workers/TexturesWorker.js +103 -18
  97. package/src/Processor/archiveFormats.js +7 -0
  98. package/src/Processor/utils.js +2 -1
  99. package/src/RayzeeRenderer.js +3899 -0
  100. package/src/RenderSettings.js +68 -62
  101. package/src/Stages/ASVGF.js +13 -14
  102. package/src/Stages/AutoExposure.js +17 -17
  103. package/src/Stages/BilateralFilter.js +2 -1
  104. package/src/Stages/Compositor.js +8 -23
  105. package/src/Stages/DenoiserSettings.js +182 -0
  106. package/src/Stages/EdgeFilter.js +2 -1
  107. package/src/Stages/MotionVector.js +1 -1
  108. package/src/Stages/NRD.js +11 -5
  109. package/src/Stages/NormalDepth.js +12 -8
  110. package/src/Stages/PathTracer.js +338 -43
  111. package/src/Stages/PathTracerStage.js +69 -66
  112. package/src/Stages/Variance.js +1 -1
  113. package/src/Storage/BLASCache.js +1 -1
  114. package/src/Storage/CDFCache.js +18 -17
  115. package/src/Storage/DownloadCache.js +1 -1
  116. package/src/Storage/SceneGraphCodec.js +46 -3
  117. package/src/Storage/SpillStore.js +1 -1
  118. package/src/Storage/StorageManager.js +45 -9
  119. package/src/Storage/areas.js +4 -0
  120. package/src/TSL/BVHTraversal.js +50 -25
  121. package/src/TSL/Bidirectional.js +204 -0
  122. package/src/TSL/BidirectionalLamps.js +132 -0
  123. package/src/TSL/Clearcoat.js +15 -6
  124. package/src/TSL/Common.js +20 -15
  125. package/src/TSL/ConnectKernel.js +147 -0
  126. package/src/TSL/Debugger.js +1 -1
  127. package/src/TSL/Displacement.js +4 -3
  128. package/src/TSL/EmissiveSampling.js +65 -18
  129. package/src/TSL/Environment.js +77 -106
  130. package/src/TSL/EnvironmentCDF.js +85 -39
  131. package/src/TSL/ExtendKernel.js +8 -3
  132. package/src/TSL/FinalWriteKernel.js +4 -4
  133. package/src/TSL/GenerateKernel.js +18 -4
  134. package/src/TSL/HitFacet.js +35 -5
  135. package/src/TSL/LightBVHSampling.js +40 -9
  136. package/src/TSL/LightGenerateKernel.js +293 -0
  137. package/src/TSL/LightGuide.js +198 -0
  138. package/src/TSL/LightSplatKernel.js +165 -0
  139. package/src/TSL/LightVisibility.js +111 -0
  140. package/src/TSL/LightsCore.js +13 -38
  141. package/src/TSL/LightsDirect.js +28 -29
  142. package/src/TSL/LightsIndirect.js +3 -1
  143. package/src/TSL/LightsSampling.js +239 -51
  144. package/src/TSL/MaterialEvaluation.js +113 -31
  145. package/src/TSL/MaterialProperties.js +44 -17
  146. package/src/TSL/MaterialTransmission.js +52 -47
  147. package/src/TSL/MergeKernel.js +218 -0
  148. package/src/TSL/PathTracerCore.js +65 -29
  149. package/src/TSL/Random.js +5 -1
  150. package/src/TSL/SceneResources.js +51 -0
  151. package/src/TSL/ShadeKernel.js +964 -101
  152. package/src/TSL/Struct.js +10 -0
  153. package/src/TSL/Sun.js +1 -1
  154. package/src/TSL/TextureSampling.js +66 -61
  155. package/src/addons/archives.js +14 -0
  156. package/src/addons/bidirectional.js +13 -0
  157. package/src/addons/color.js +32 -0
  158. package/src/addons/formats.js +16 -0
  159. package/src/addons/physicalSky.js +14 -0
  160. package/src/addons/storage.js +19 -0
  161. package/src/core.js +31 -0
  162. package/src/index.js +13 -23
  163. package/src/integrators/BidirectionalIntegrator.js +833 -0
  164. package/src/managers/AnimationManager.js +88 -6
  165. package/src/managers/CameraManager.js +103 -46
  166. package/src/managers/DenoisingManager.js +14 -10
  167. package/src/managers/EnvironmentManager.js +125 -103
  168. package/src/managers/GoboManager.js +9 -0
  169. package/src/managers/MaterialDataManager.js +48 -37
  170. package/src/managers/UniformManager.js +32 -10
  171. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  172. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  173. 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,22 @@ export class PathTracer extends PathTracerStage {
1208
1443
 
1209
1444
  }
1210
1445
 
1446
+ /** The material layers the scene uses; bidirectional leaves diffuse transmission out (its paths do not cross surfaces). */
1447
+ _layersToCompile() {
1448
+
1449
+ const layers = this.materialData.materialLayers();
1450
+ if ( this._integrator ) layers.diffuseTransmission = false;
1451
+ return layers;
1452
+
1453
+ }
1454
+
1455
+ /** Whether the kernels write the G-buffer: asked for directly, or for the hit distance that rides in it. */
1456
+ get _gBufferCompiled() {
1457
+
1458
+ return this._outputs.has( 'gBuffer' ) || this._outputs.has( 'hitDistance' );
1459
+
1460
+ }
1461
+
1211
1462
  _buildWavefrontKernels() {
1212
1463
 
1213
1464
  const texNodes = this.shaderBuilder.getSceneTextureNodes();
@@ -1215,6 +1466,8 @@ export class PathTracer extends PathTracerStage {
1215
1466
 
1216
1467
  // A fresh build binds the current lightStorageAttr — any pending realloc is covered.
1217
1468
  this._lightBufferRealloc = false;
1469
+ this._outputsChanged = false;
1470
+ this._integrator?.beforeKernelBuild();
1218
1471
 
1219
1472
  const w = this.storageTextures.renderWidth;
1220
1473
  const h = this.storageTextures.renderHeight;
@@ -1231,13 +1484,14 @@ export class PathTracer extends PathTracerStage {
1231
1484
 
1232
1485
  // Per-path buffers (RAY/HIT/rng) sized to the budget B and indexed by LOCAL slot r ∈ [0,B). _cap = B is
1233
1486
  // baked into the SoA stride but never changes with resolution, so this build happens once (model load).
1487
+ const lightVertices = this._integrator?.lightVertices ?? 0;
1234
1488
  if ( ! this._packedBuffers ) {
1235
1489
 
1236
- this._packedBuffers = new PackedRayBuffer( B, this.renderer );
1490
+ this._packedBuffers = new PackedRayBuffer( B, this.renderer, lightVertices );
1237
1491
 
1238
1492
  } else {
1239
1493
 
1240
- this._packedBuffers.resize( B );
1494
+ this._packedBuffers.resize( B, lightVertices );
1241
1495
 
1242
1496
  }
1243
1497
 
@@ -1275,6 +1529,8 @@ export class PathTracer extends PathTracerStage {
1275
1529
 
1276
1530
  }
1277
1531
 
1532
+ this._integrator?.allocate( maxPixels );
1533
+
1278
1534
  const m2RW = storage( this._m2Attr, 'float' );
1279
1535
  const streakRW = storage( this._streakAttr, 'uint' );
1280
1536
  const streakRO = storage( this._streakAttr, 'uint' ).toReadOnly();
@@ -1310,6 +1566,11 @@ export class PathTracer extends PathTracerStage {
1310
1566
  const prevNormalDepth = this.shaderBuilder.prevNormalDepthTexNode;
1311
1567
  const writeTex = this.storageTextures.getWriteTextures();
1312
1568
 
1569
+ const picked = this._pickedShadowRays();
1570
+ this._compiledPick = picked;
1571
+ const learnsVisibility = picked !== null;
1572
+ this._learnsVisibility = learnsVisibility;
1573
+ qm.setVisibilityTable( learnsVisibility );
1313
1574
  const counters = qm.getCounters();
1314
1575
 
1315
1576
  // Copy ACTIVE_RAY_COUNT into bounceCounts[currentBounce] for the readback survivor curve.
@@ -1393,6 +1654,8 @@ export class PathTracer extends PathTracerStage {
1393
1654
  transmissiveBounces: this.transmissiveBounces,
1394
1655
  transparentBackground: this.transparentBackground,
1395
1656
  auxGBufferEnabled: this._auxGBufferUniform,
1657
+ gBuffer: this._gBufferCompiled,
1658
+ bidirectional: this._integrator?.uniforms ?? null,
1396
1659
  };
1397
1660
  const genFn = buildGenerateKernel( genParams );
1398
1661
  this._kernelManager.register( 'generate',
@@ -1586,13 +1849,21 @@ export class PathTracer extends PathTracerStage {
1586
1849
  const _mat = this.materialData;
1587
1850
  const _env = this.environment;
1588
1851
  // 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.
1852
+ // every runtime branch references a valid node.
1591
1853
  const freshSrgbBuckets = buildBucketTextureNodes( _mat.srgbBuckets );
1592
1854
  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 );
1855
+ // This renderer's resources, in the build context of each kernel that samples them (SceneResources.js).
1856
+ // Alpha-cutout shadow rays sample albedo (sRGB pool).
1857
+ const resources = {
1858
+ srgbBuckets: freshSrgbBuckets,
1859
+ linearBuckets: freshLinearBuckets,
1860
+ shadowAlbedoMaps: freshSrgbBuckets,
1861
+ goboMaps: texNodes.goboMapsTex,
1862
+ iesProfiles: texNodes.iesProfilesTex,
1863
+ alphaShadows: this.uniforms.get( 'enableAlphaShadows' ),
1864
+ materialLayers: this._compiledLayers = this._layersToCompile(),
1865
+ };
1866
+ const own = ( call ) => withSceneResources( call, resources );
1596
1867
  const freshEnvTex = _env.environmentTexture ? texture( _env.environmentTexture ) : texNodes.envTex;
1597
1868
 
1598
1869
  this._wfTexNodes = {
@@ -1603,8 +1874,7 @@ export class PathTracer extends PathTracerStage {
1603
1874
  };
1604
1875
 
1605
1876
  // Material-coherence sort gate (experiment): only worthwhile above a few materials.
1606
- this._sortMaterials = ( ENGINE_DEFAULTS.wavefrontSortMaterials ?? false )
1607
- && ( this.materialData?.materialCount ?? 0 ) > 8;
1877
+ this._sortMaterials = this.sortMaterials && ( this.materialData?.materialCount ?? 0 ) > 8;
1608
1878
 
1609
1879
  const extFn = buildExtendKernel( {
1610
1880
  bvhBuffer: freshBvh,
@@ -1669,7 +1939,6 @@ export class PathTracer extends PathTracerStage {
1669
1939
 
1670
1940
  const shadeFn = buildShadeKernel( {
1671
1941
  gBufferRW,
1672
- envCompensationDelta: this.envCompensationDelta,
1673
1942
  bvhBuffer: freshBvh,
1674
1943
  triangleBuffer: freshTri,
1675
1944
  materialBuffer: freshMat,
@@ -1721,7 +1990,6 @@ export class PathTracer extends PathTracerStage {
1721
1990
  emissiveVec4Offset: this.emissiveVec4Offset,
1722
1991
  emissiveTotalPower: this.emissiveTotalPower,
1723
1992
  emissiveBoost: this.emissiveBoost,
1724
- totalTriangleCount: this.totalTriangleCount,
1725
1993
  enableEmissiveTriangleSampling: this.enableEmissiveTriangleSampling,
1726
1994
  lightBVHNodeCount: this.lightBVHNodeCount,
1727
1995
  reverseMapVec4Offset: this.reverseMapVec4Offset,
@@ -1729,17 +1997,29 @@ export class PathTracer extends PathTracerStage {
1729
1997
  maxRayCount: this._wfMaxRayCount,
1730
1998
  chunkRowBase: this._wfChunkRowBase,
1731
1999
  auxGBufferEnabled: this._auxGBufferUniform,
2000
+ gBuffer: this._gBufferCompiled,
1732
2001
  hasSun: this.hasSun,
1733
2002
  sunDirection: this.sunDirection,
1734
2003
  sunRadiance: this.sunRadiance,
1735
2004
  sunParams: this.sunParams,
2005
+ bidirectional: this._integrator?.uniforms ?? null,
2006
+ hitDistanceEncode: this._outputs.get( 'hitDistance' )?.encode ?? null,
2007
+ materialLayers: this._compiledLayers,
2008
+ shadowRays: picked,
2009
+ cameraWorldMatrix: this.cameraWorldMatrix,
1736
2010
  } );
1737
2011
  this._kernelManager.register( 'shade',
1738
- shadeFn().compute(
2012
+ own( shadeFn() ).compute(
1739
2013
  [ Math.ceil( maxRays / SHADE_WG_SIZE ), 1, 1 ],
1740
2014
  [ SHADE_WG_SIZE, 1, 1 ]
1741
2015
  )
1742
2016
  );
2017
+ if ( learnsVisibility ) {
2018
+
2019
+ this._kernelManager.register( 'visibilityClear', buildVisibilityClearKernel( { counters } ) );
2020
+ this._kernelManager.register( 'visibilityFold', buildVisibilityFoldKernel( { counters } ) );
2021
+
2022
+ }
1743
2023
 
1744
2024
  // Subgroup prefix-sum variant when supported.
1745
2025
  const subgroupsOK = this._useSubgroupCompact
@@ -1801,6 +2081,11 @@ export class PathTracer extends PathTracerStage {
1801
2081
  copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
1802
2082
  );
1803
2083
 
2084
+ this._integrator?.registerKernels( {
2085
+ km: this._kernelManager, qm, pb, counters, bounceCountsBuf, wfCurrentBounce, copyReadB, copyWriteA, maxRays, w, own,
2086
+ freshLight, freshTri, freshBvh, freshMat, freshEnvTex, freshEnvCDF,
2087
+ } );
2088
+
1804
2089
  const fwFn = buildFinalWriteKernel( {
1805
2090
  rayBufferRO: pb.rayBuffer.ro,
1806
2091
  gBufferRO,
@@ -1823,6 +2108,7 @@ export class PathTracer extends PathTracerStage {
1823
2108
  renderHeight: this._wfRenderHeight,
1824
2109
  visMode: this.visMode,
1825
2110
  auxGBufferEnabled: this._auxGBufferUniform,
2111
+ gBuffer: this._gBufferCompiled,
1826
2112
  cleanAuxNormalEnabled: this._cleanAuxNormalUniform,
1827
2113
  m2BufferRW: m2RW,
1828
2114
  useAdaptiveSampling: this.useAdaptiveSampling,
@@ -1882,14 +2168,16 @@ export class PathTracer extends PathTracerStage {
1882
2168
  debugVisScale: this.debugVisScale,
1883
2169
  frame: this.frame,
1884
2170
  } );
2171
+ // Only the debug views dispatch it, so it compiles when one is first chosen.
1885
2172
  this._kernelManager.register( 'debug',
1886
- debugFn().compute(
2173
+ own( debugFn() ).compute(
1887
2174
  [ Math.ceil( w / DEBUG_WG_SIZE ), Math.ceil( h / DEBUG_WG_SIZE ), 1 ],
1888
2175
  [ DEBUG_WG_SIZE, DEBUG_WG_SIZE, 1 ]
1889
- )
2176
+ ),
2177
+ { eager: false },
1890
2178
  );
1891
2179
 
1892
- this._wavefrontReady = true;
2180
+ this._compileKernels();
1893
2181
 
1894
2182
  const bufferBytes = ( this._packedBuffers?.totalBytes ?? 0 ) + ( this._queueManager?.totalBytes ?? 0 );
1895
2183
 
@@ -1898,6 +2186,7 @@ export class PathTracer extends PathTracerStage {
1898
2186
  `${fmt.mb( bufferBytes )} wavefront buffers`,
1899
2187
  `budget ${fmt.n( B )} paths`,
1900
2188
  this._numChunks > 1 ? `${this._numChunks} chunks of ≤${this._chunkRows} rows` : null,
2189
+ this._integrator?.describe() ?? null,
1901
2190
  ] ) );
1902
2191
 
1903
2192
  }
@@ -1930,6 +2219,7 @@ export class PathTracer extends PathTracerStage {
1930
2219
  const km = this._kernelManager;
1931
2220
  km.setDispatchForGrid( 'generate', w, rows );
1932
2221
  km.setDispatchForGrid( 'finalWrite', w, rows );
2222
+ km.setDispatchForGrid( 'splatResolve', w, rows );
1933
2223
  km.setDispatchForCount( 'initActiveIndices', chunkPixels );
1934
2224
  km.setDispatchForCount( 'buildActivePixels', chunkPixels );
1935
2225
 
@@ -1947,6 +2237,9 @@ export class PathTracer extends PathTracerStage {
1947
2237
  this._m2Attr?.dispose?.();
1948
2238
  this._streakAttr?.dispose?.();
1949
2239
  this._frozenMaskAttr?.dispose?.();
2240
+ for ( const integrator of this._integratorInstances.values() ) integrator.dispose();
2241
+ this._integratorInstances.clear();
2242
+ this._integrator = null;
1950
2243
  this._packedBuffers = null;
1951
2244
  this._queueManager = null;
1952
2245
  this._kernelManager = null;
@@ -1956,6 +2249,8 @@ export class PathTracer extends PathTracerStage {
1956
2249
  this._frozenMaskAttr = null;
1957
2250
  this._convDebugSource = null;
1958
2251
  this._wavefrontReady = false;
2252
+ this._kernelGeneration ++;
2253
+ this._kernelsCompiling = null;
1959
2254
 
1960
2255
  }
1961
2256