@meimu/mutracer 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSES/FidelityFX-FSR.txt +19 -0
  3. package/LICENSES/pbrt-v4-Apache-2.0.txt +202 -0
  4. package/README.md +3 -0
  5. package/THIRD_PARTY_NOTICES.md +18 -4
  6. package/dist/assets/bvh.worker.js +1 -1
  7. package/dist/mutracer.umd.js +9328 -7614
  8. package/docs/api.md +83 -35
  9. package/docs/caustics.md +80 -0
  10. package/docs/materials.md +9 -6
  11. package/docs/performance.md +7 -0
  12. package/docs/post-processing.md +62 -9
  13. package/docs/progressive-rendering.md +53 -9
  14. package/docs/sampling.md +4 -0
  15. package/docs/technical/acceleration.md +52 -0
  16. package/docs/technical/aovs.md +79 -0
  17. package/docs/technical/caustics.md +109 -0
  18. package/docs/technical/continuous-spectrum.md +122 -0
  19. package/docs/technical/display-upscale.md +82 -0
  20. package/docs/technical/openpbr.md +99 -0
  21. package/docs/technical/pbrt-import.md +168 -0
  22. package/docs/technical/tiling.md +74 -0
  23. package/docs/technical/webgl.md +53 -0
  24. package/docs/updates.md +3 -1
  25. package/docs/viewer-checklist.md +65 -0
  26. package/docs/viewer.md +18 -6
  27. package/lib/Display.d.ts +19 -5
  28. package/lib/Display.js +10 -7
  29. package/lib/Display.js.map +1 -1
  30. package/lib/Engine.d.ts +4 -1
  31. package/lib/Engine.js +77 -14
  32. package/lib/Engine.js.map +1 -1
  33. package/lib/Render.d.ts +81 -24
  34. package/lib/Render.js +201 -26
  35. package/lib/Render.js.map +1 -1
  36. package/lib/RenderLoop.d.ts +7 -3
  37. package/lib/RenderLoop.js +68 -44
  38. package/lib/RenderLoop.js.map +1 -1
  39. package/lib/main.d.ts +4 -2
  40. package/lib/main.js +1 -0
  41. package/lib/main.js.map +1 -1
  42. package/lib/oidn.d.ts +1 -1
  43. package/lib/renderer/AovPrimitiveCache.js +39 -8
  44. package/lib/renderer/AovPrimitiveCache.js.map +1 -1
  45. package/lib/renderer/DisplayPostProcess.d.ts +8 -3
  46. package/lib/renderer/DisplayPostProcess.js +51 -18
  47. package/lib/renderer/DisplayPostProcess.js.map +1 -1
  48. package/lib/renderer/GPUTraceQueue.d.ts +2 -1
  49. package/lib/renderer/GPUTraceQueue.js +3 -1
  50. package/lib/renderer/GPUTraceQueue.js.map +1 -1
  51. package/lib/renderer/MaterialData.js +7 -2
  52. package/lib/renderer/MaterialData.js.map +1 -1
  53. package/lib/renderer/RenderingPipeline.d.ts +11 -1
  54. package/lib/renderer/RenderingPipeline.js.map +1 -1
  55. package/lib/renderer/TileRender.d.ts +2 -2
  56. package/lib/renderer/TileRender.js +128 -7
  57. package/lib/renderer/TileRender.js.map +1 -1
  58. package/lib/renderer/WGPUAov.d.ts +6 -1
  59. package/lib/renderer/WGPUAov.js +108 -14
  60. package/lib/renderer/WGPUAov.js.map +1 -1
  61. package/lib/renderer/WGPUCanvasSurface.js +12 -1
  62. package/lib/renderer/WGPUCanvasSurface.js.map +1 -1
  63. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  64. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  65. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  66. package/lib/renderer/WGPUEnvMapDistribution.js +19 -19
  67. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  68. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  69. package/lib/renderer/WGPUPhotonMap.js +191 -0
  70. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  71. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  72. package/lib/renderer/WGPURayTraceOutput.js +122 -28
  73. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  74. package/lib/renderer/WGPURayTracePass.d.ts +65 -21
  75. package/lib/renderer/WGPURayTracePass.js +730 -409
  76. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  77. package/lib/renderer/WGPURenderingPipeline.js +110 -68
  78. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  79. package/lib/renderer/WebGLAov.js +3 -3
  80. package/lib/renderer/WebGLAov.js.map +1 -1
  81. package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
  82. package/lib/renderer/WebGLFullQuadPass.js +10 -0
  83. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  84. package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
  85. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
  86. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  87. package/lib/renderer/WebGLRayTracePass.d.ts +20 -11
  88. package/lib/renderer/WebGLRayTracePass.js +157 -116
  89. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  90. package/lib/renderer/WebGLRenderingPipeline.js +17 -14
  91. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  92. package/lib/renderer/WebGLSkyBaker.js +60 -60
  93. package/lib/renderer/accumulationTarget.d.ts +2 -1
  94. package/lib/renderer/accumulationTarget.js.map +1 -1
  95. package/lib/renderer/aov.d.ts +13 -1
  96. package/lib/renderer/aov.js +21 -2
  97. package/lib/renderer/aov.js.map +1 -1
  98. package/lib/renderer/aovDisplay.js +2 -2
  99. package/lib/renderer/aovDisplay.js.map +1 -1
  100. package/lib/renderer/binnedSahBvh.d.ts +2 -1
  101. package/lib/renderer/binnedSahBvh.js +24 -17
  102. package/lib/renderer/binnedSahBvh.js.map +1 -1
  103. package/lib/renderer/bvhAccel.d.ts +3 -1
  104. package/lib/renderer/bvhAccel.js +36 -6
  105. package/lib/renderer/bvhAccel.js.map +1 -1
  106. package/lib/renderer/cameraUniform.d.ts +1 -1
  107. package/lib/renderer/cameraUniform.js +6 -2
  108. package/lib/renderer/cameraUniform.js.map +1 -1
  109. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  110. package/lib/renderer/causticDiagnostics.js +5 -0
  111. package/lib/renderer/causticDiagnostics.js.map +1 -0
  112. package/lib/renderer/causticEmission.d.ts +10 -0
  113. package/lib/renderer/causticEmission.js +40 -0
  114. package/lib/renderer/causticEmission.js.map +1 -0
  115. package/lib/renderer/causticUniform.d.ts +12 -0
  116. package/lib/renderer/causticUniform.js +22 -0
  117. package/lib/renderer/causticUniform.js.map +1 -0
  118. package/lib/renderer/caustics.d.ts +16 -0
  119. package/lib/renderer/caustics.js +93 -0
  120. package/lib/renderer/caustics.js.map +1 -0
  121. package/lib/renderer/compactVertices.d.ts +14 -0
  122. package/lib/renderer/compactVertices.js +124 -0
  123. package/lib/renderer/compactVertices.js.map +1 -0
  124. package/lib/renderer/easu.d.ts +11 -0
  125. package/lib/renderer/easu.js +156 -0
  126. package/lib/renderer/easu.js.map +1 -0
  127. package/lib/renderer/geometryUpdateState.d.ts +1 -0
  128. package/lib/renderer/geometryUpdateState.js.map +1 -1
  129. package/lib/renderer/geometryUpdates.js +3 -2
  130. package/lib/renderer/geometryUpdates.js.map +1 -1
  131. package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
  132. package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
  133. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
  134. package/lib/renderer/glsl/aovTrace.frag.js +45 -37
  135. package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
  136. package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
  137. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
  138. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
  139. package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
  140. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
  141. package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
  142. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  143. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  144. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  145. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
  146. package/lib/renderer/glsl/chunks/intersect.glsl.js +478 -465
  147. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  148. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  149. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
  150. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  151. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
  152. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  153. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  154. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  155. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +70 -63
  156. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  157. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +89 -75
  158. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  159. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  160. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +160 -118
  161. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  162. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
  163. package/lib/renderer/glsl/chunks/sampling.glsl.js +88 -91
  164. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  165. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  166. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.d.ts +6 -0
  167. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
  168. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
  169. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  170. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  171. package/lib/renderer/glsl/depthQuery.frag.js +13 -7
  172. package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
  173. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
  174. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  175. package/lib/renderer/glsl/rayTrace.frag.js +338 -252
  176. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  177. package/lib/renderer/glsl/shadowCatcher.frag.js +27 -26
  178. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  179. package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
  180. package/lib/renderer/gpuMemory.js +1 -1
  181. package/lib/renderer/gpuMemory.js.map +1 -1
  182. package/lib/renderer/lightSampling.d.ts +3 -2
  183. package/lib/renderer/lightSampling.js +14 -8
  184. package/lib/renderer/lightSampling.js.map +1 -1
  185. package/lib/renderer/openPbrEnergyLut.d.ts +2 -1
  186. package/lib/renderer/openPbrEnergyLut.js +18 -1
  187. package/lib/renderer/openPbrEnergyLut.js.map +1 -1
  188. package/lib/renderer/pathLimits.d.ts +5 -4
  189. package/lib/renderer/pathLimits.js +6 -5
  190. package/lib/renderer/pathLimits.js.map +1 -1
  191. package/lib/renderer/prepareMeshBVH.d.ts +2 -0
  192. package/lib/renderer/prepareMeshBVH.js +5 -3
  193. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  194. package/lib/renderer/progressiveScheduler.js +5 -4
  195. package/lib/renderer/progressiveScheduler.js.map +1 -1
  196. package/lib/renderer/renderPipeline.d.ts +5 -0
  197. package/lib/renderer/renderPipeline.js +1 -0
  198. package/lib/renderer/renderPipeline.js.map +1 -1
  199. package/lib/renderer/researchTransport.d.ts +31 -0
  200. package/lib/renderer/researchTransport.js +5 -0
  201. package/lib/renderer/researchTransport.js.map +1 -0
  202. package/lib/renderer/sceneAssembly.d.ts +1 -1
  203. package/lib/renderer/sceneAssembly.js +4 -3
  204. package/lib/renderer/sceneAssembly.js.map +1 -1
  205. package/lib/renderer/sceneChangeDetector.d.ts +1 -1
  206. package/lib/renderer/sceneChangeDetector.js +3 -1
  207. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  208. package/lib/renderer/sceneLayoutSchema.d.ts +2 -0
  209. package/lib/renderer/sceneLayoutSchema.js +3 -3
  210. package/lib/renderer/sceneLayoutSchema.js.map +1 -1
  211. package/lib/renderer/scenePreparation.d.ts +3 -2
  212. package/lib/renderer/scenePreparation.js +18 -2
  213. package/lib/renderer/scenePreparation.js.map +1 -1
  214. package/lib/renderer/scratchSampling.js +6 -6
  215. package/lib/renderer/spectrum.d.ts +21 -0
  216. package/lib/renderer/spectrum.js +154 -0
  217. package/lib/renderer/spectrum.js.map +1 -0
  218. package/lib/renderer/staticBvhPreparation.d.ts +9 -0
  219. package/lib/renderer/staticBvhPreparation.js +8 -2
  220. package/lib/renderer/staticBvhPreparation.js.map +1 -1
  221. package/lib/renderer/surfaceHit.d.ts +10 -0
  222. package/lib/renderer/surfaceHit.js +22 -0
  223. package/lib/renderer/surfaceHit.js.map +1 -0
  224. package/lib/renderer/tangentRequirements.d.ts +2 -0
  225. package/lib/renderer/tangentRequirements.js +7 -0
  226. package/lib/renderer/tangentRequirements.js.map +1 -0
  227. package/lib/renderer/textureAtlas.d.ts +4 -0
  228. package/lib/renderer/textureAtlas.js +48 -2
  229. package/lib/renderer/textureAtlas.js.map +1 -1
  230. package/lib/renderer/webgl.js +4 -0
  231. package/lib/renderer/webgl.js.map +1 -1
  232. package/lib/renderer/webglLightSampling.d.ts +39 -0
  233. package/lib/renderer/webglLightSampling.js +15 -0
  234. package/lib/renderer/webglLightSampling.js.map +1 -0
  235. package/lib/renderer/webglVertexAttributes.d.ts +17 -0
  236. package/lib/renderer/webglVertexAttributes.js +44 -0
  237. package/lib/renderer/webglVertexAttributes.js.map +1 -0
  238. package/lib/renderer/wgpuBindlessSource.js +3 -3
  239. package/lib/renderer/wgpuMaterialConstants.js +4 -3
  240. package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
  241. package/lib/renderer/wgpuSceneUniform.js +2 -2
  242. package/lib/renderer/wgpuSceneUniform.js.map +1 -1
  243. package/lib/renderer/wgpuShaderSource.d.ts +22 -0
  244. package/lib/renderer/wgpuShaderSource.js +3 -1
  245. package/lib/renderer/wgpuShaderSource.js.map +1 -1
  246. package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
  247. package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
  248. package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
  249. package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
  250. package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
  251. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
  252. package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +4 -0
  253. package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
  254. package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
  255. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
  256. package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
  257. package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
  258. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
  259. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
  260. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
  261. package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
  262. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  263. package/lib/renderer/wgsl/causticLight.compute.js +539 -0
  264. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  265. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  266. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  267. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  268. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  269. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  270. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  271. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  272. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  273. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  274. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  275. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  276. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  277. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  278. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
  279. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -2
  280. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +634 -620
  281. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  282. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
  283. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +23 -21
  284. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
  285. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +3 -1
  286. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +77 -74
  287. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  288. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +795 -795
  289. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
  290. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  291. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +5 -1
  292. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +45 -29
  293. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  294. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  295. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
  296. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  297. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  298. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +228 -189
  299. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  300. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  301. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
  302. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  303. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  304. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +88 -92
  305. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  306. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  307. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.d.ts +6 -0
  308. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
  309. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
  310. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  311. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
  312. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
  313. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
  314. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
  315. package/lib/renderer/wgsl/compose.frag.wgsl.js +100 -98
  316. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  317. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  318. package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
  319. package/lib/renderer/wgsl/depthQuery.wgsl.js +12 -3
  320. package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
  321. package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
  322. package/lib/renderer/wgsl/easu.wgsl.js +80 -0
  323. package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
  324. package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
  325. package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
  326. package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
  327. package/lib/renderer/wgsl/jnlm.compute.js +74 -74
  328. package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
  329. package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
  330. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
  331. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  332. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  333. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  334. package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
  335. package/lib/renderer/wgsl/rayTrace.compute.d.ts +23 -1
  336. package/lib/renderer/wgsl/rayTrace.compute.js +531 -543
  337. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  338. package/lib/scene/MaterialState.d.ts +2 -2
  339. package/lib/scene/MaterialState.js +2 -2
  340. package/lib/scene/MaterialState.js.map +1 -1
  341. package/lib/scene/Scene.js.map +1 -1
  342. package/lib/settings/render.d.ts +22 -0
  343. package/lib/settings/render.js +2 -0
  344. package/lib/settings/render.js.map +1 -1
  345. package/lib/util/fromTHREE.js +23 -16
  346. package/lib/util/fromTHREE.js.map +1 -1
  347. package/lib/version.d.ts +1 -1
  348. package/lib/version.js +1 -1
  349. package/lib/version.js.map +1 -1
  350. package/llms.txt +2 -1
  351. package/package.json +7 -4
  352. package/lib/oidnBundledWeights.cdn.d.ts +0 -2
  353. package/lib/oidnBundledWeights.cdn.js +0 -5
  354. package/lib/oidnBundledWeights.cdn.js.map +0 -1
  355. package/lib/renderer/bvh.worker.d.ts +0 -1
  356. package/lib/renderer/bvh.worker.js +0 -16
  357. package/lib/renderer/bvh.worker.js.map +0 -1
  358. package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
  359. package/lib/renderer/bvhWorkerBuild.js +0 -17
  360. package/lib/renderer/bvhWorkerBuild.js.map +0 -1
  361. package/lib/renderer/lightCore.d.ts +0 -12
  362. package/lib/renderer/lightCore.js +0 -13
  363. package/lib/renderer/lightCore.js.map +0 -1
  364. package/lib/renderer/materialCore.d.ts +0 -8
  365. package/lib/renderer/materialCore.js +0 -9
  366. package/lib/renderer/materialCore.js.map +0 -1
  367. package/lib/scene/PixelTexture.d.ts +0 -6
  368. package/lib/scene/PixelTexture.js +0 -10
  369. package/lib/scene/PixelTexture.js.map +0 -1
  370. package/lib/util/studioLighting.d.ts +0 -18
  371. package/lib/util/studioLighting.js +0 -36
  372. package/lib/util/studioLighting.js.map +0 -1
@@ -1,9 +1,19 @@
1
+ import { resolveSurfaceHit } from './surfaceHit.js';
2
+ import { compactVertexLayout, packCompactVertices, packTriangleMaterials } from './compactVertices.js';
3
+ import { causticDiagnostics } from './causticDiagnostics.js';
4
+ import { WGPUPhotonMap, photonRadius } from './WGPUPhotonMap.js';
5
+ import { defaultPhotonMapPhotons } from './wgsl/photonMap.compute.js';
6
+ import { researchTransport } from './researchTransport.js';
7
+ import { causticEmissionTable } from './causticEmission.js';
8
+ import { WGPUCausticLightPass } from './WGPUCausticLightPass.js';
9
+ import { causticLightCompute } from './wgsl/causticLight.compute.js';
10
+ import { causticCasterSpheres, hasStochasticCausticCaster, causticCasterSphere, isCausticSpecular } from './caustics.js';
1
11
  import { AovPrimitiveCache } from './AovPrimitiveCache.js';
2
12
  import { packAovScene } from './aovScene.js';
3
- import { aovMask } from './aov.js';
13
+ import { aovMask, AOV_ACCUMULATED_MASK, AOV_REFLECTANCE_MASK, COMPACT_AOV_KINDS } from './aov.js';
4
14
  import { proceduralEnvironmentPower } from './proceduralEnvironmentPower.js';
5
15
  import { WGPUSkyBaker } from './WGPUSkyBaker.js';
6
- import { initialSceneConstants, sceneAttributeLayout, sceneTraversalConstants, prepareSceneLightSampling, writeTraceLighting, packSceneAnalyticLights, writeTraceQuality, traceSampling, writeTraceSampling } from './sceneAssembly.js';
16
+ import { initialSceneConstants, sceneTraversalConstants, prepareSceneLightSampling, writeTraceLighting, packSceneAnalyticLights, writeTraceQuality, traceSampling, writeTraceSampling } from './sceneAssembly.js';
7
17
  import { rayTraceBindGroupLayouts } from './sceneLayoutSchema.js';
8
18
  import { WGPUBindlessMaterialData } from './WGPUBindlessMaterialData.js';
9
19
  import { WGPUBindlessTextures } from './WGPUBindlessTextures.js';
@@ -14,7 +24,7 @@ import { wgpuShaderSource, wgpuPipelineConstantKeys, WGPU_SHADER_DEFINE_KEYS } f
14
24
  import { makeRayTraceUniform, WGPU_SCENE_BUFFER_MIN_BYTES } from './wgpuSceneUniform.js';
15
25
  import { constants } from './webgl.js';
16
26
  import { MATERIAL_FLOAT_COUNT } from './materialLayoutSchema.js';
17
- import { normalizeBounces } from './pathLimits.js';
27
+ import { normalizeBounces, MAX_PATH_EVENTS_WGPU } from './pathLimits.js';
18
28
  import { updateMeshInstanceColors } from './prepareMeshBVH.js';
19
29
  import { updateTLASInstanceColors } from './bvhAccel.js';
20
30
  import { getMaterialHashKey } from '../scene/openPbrSchema.js';
@@ -27,8 +37,7 @@ import { packCameraUniform } from './cameraUniform.js';
27
37
  import { SHADOW_CATCHER_ACCUMULATION_BYTES } from './wgsl/chunks/shadowCatcherAccumulation.wgsl.js';
28
38
  import { DEPTH_SAMPLE_COUNT, packDepthSampleArea, unpackDepthSamples } from './depthQuery.js';
29
39
  import { MeshMembershipCache } from './meshMembership.js';
30
- import { mergeRefitRanges } from './bvhAccel.js';
31
- import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, packVertexAttributes } from './geometryUpdates.js';
40
+ import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, } from './geometryUpdates.js';
32
41
  import { SceneEnvironmentCache, RectLightTextureTracker } from './sceneLightChanges.js';
33
42
  import { MaterialChangeTracker, materialIsEmissive } from './materialChanges.js';
34
43
  import { WGPUEnvMapDistribution } from './WGPUEnvMapDistribution.js';
@@ -43,10 +52,10 @@ import { generateBackgroundMapFromSceneBackground, environmentRotation, backgrou
43
52
  import { WORKGROUP_SIZE } from './wgsl/rayTrace.compute.js';
44
53
  import { envMapDistribution, environmentMapSourcesMatch } from './envMapDistribution.js';
45
54
  import { updateMeshInstances, updateMeshInstanceTransforms } from './prepareMeshBVH.js';
46
- import { buildTLAS, concatBLASBuffers, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
55
+ import { buildWebTLAS, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
47
56
  import { isTextureDataSource } from '../scene/Texture.js';
48
57
  import { DEFAULT_BEVEL_SAMPLES, DEFAULT_DIRECT_LIGHT_CANDIDATES, normalizeBevelSamples, normalizeDirectLightCandidates } from './RenderingPipeline.js';
49
- import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower, } from './lightSampling.js';
58
+ import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower } from './lightSampling.js';
50
59
  import { getOpenPbrEnergyLutData, OPENPBR_ENERGY_LUT_HEIGHT, OPENPBR_ENERGY_LUT_WIDTH } from './openPbrEnergyLut.js';
51
60
  import { offlineAdaptiveCompute } from './wgsl/offlineAdaptive.compute.js';
52
61
  import { normalizeOfflineAdaptiveSamplingOptions } from './OfflineAdaptiveSampling.js';
@@ -78,6 +87,10 @@ function joinWebGPUErrors(...errors) {
78
87
  .filter(Boolean))
79
88
  ].join('\n');
80
89
  }
90
+ const MAX_TRACE_VARIANTS = 32;
91
+ // Requests are cheap but drivers are not, and a running compilation cannot be cancelled.
92
+ const MAX_CONCURRENT_COMPILES = 2;
93
+ const MAX_QUEUED_COMPILES = 4;
81
94
  function shaderCompilationNow() {
82
95
  return typeof performance === 'undefined' ? Date.now() : performance.now();
83
96
  }
@@ -113,6 +126,16 @@ class WGPURayTracePass {
113
126
  _width = 0;
114
127
  _height = 0;
115
128
  _meshes = [];
129
+ _causticKey = '';
130
+ _photonMapping = false;
131
+ _photonMap;
132
+ _causticLight;
133
+ _causticSettings;
134
+ _causticPerspective = true;
135
+ _causticCameraScale = 1;
136
+ _causticBounds = [0, 0, 0, 0];
137
+ _transport;
138
+ _lightTracing = false;
116
139
  _sceneEnvironment = new SceneEnvironmentCache();
117
140
  _rectLightTextures = new RectLightTextureTracker();
118
141
  _analyticLights = [];
@@ -147,6 +170,8 @@ class WGPURayTracePass {
147
170
  if (!output)
148
171
  return;
149
172
  this._targetOutputs.delete(target);
173
+ this._causticLight?.release(target);
174
+ this._photonMap?.release(target);
150
175
  for (const buffer of output.buffers)
151
176
  buffer.destroy();
152
177
  if (this._activeTarget === target) {
@@ -155,17 +180,23 @@ class WGPURayTracePass {
155
180
  }
156
181
  }
157
182
  getTargetMemoryBytes(target) {
158
- return this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0;
183
+ return ((this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0) +
184
+ (this._causticLight?.bytes(target) ?? 0) +
185
+ (this._photonMap?.bytes(target) ?? 0));
159
186
  }
160
187
  useTarget(target) {
188
+ if (this._photonMapping && this._activeTarget !== target)
189
+ this._needsPhotonTargetBindGroups = true;
161
190
  this._activeTarget = target;
162
191
  const output = this._targetOutputs.get(target);
163
- [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] = output?.buffers ?? [];
192
+ [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
193
+ output?.buffers ?? [];
164
194
  if (output?.bindGroup && output.pipeline === this._rayTracePipeline && this._rayTraceBindGroups) {
165
195
  this._rayTraceBindGroups[this._bindless ? 2 : 3] = output.bindGroup;
196
+ this._needsUpdateOutputBindGroup = false;
166
197
  }
167
198
  else {
168
- this._needsUpdateBindGroups = true;
199
+ this._needsUpdateOutputBindGroup = true;
169
200
  }
170
201
  }
171
202
  _outColorBuffer;
@@ -212,22 +243,25 @@ class WGPURayTracePass {
212
243
  _skyBaker;
213
244
  _backgroundImageSource;
214
245
  _backgroundTex;
215
- _rayTracePipelineLayout;
216
246
  _rayTraceBindGroups;
247
+ _photonEmissionGroups;
217
248
  _blasLayoutMask = 1;
218
- _compiledBlasLayoutMask = 0;
219
- _compiledAnisotropyDirections = false;
220
- _compiledCoatNormals = false;
221
- _compiledHasAlpha = true;
222
- _compiledSceneHasAlpha = true;
223
249
  _alphaBlendStrategy = 'stochastic';
224
250
  _constants = initialSceneConstants();
225
251
  _retainedShaderCapabilities;
226
252
  _retainedShaderStructure;
227
253
  _shaderCapabilities = new WGPUShaderCapabilities();
228
- _prevPipelineConstants = {};
229
- _prevDefinesCode = '';
230
- _prevPipelineInputs;
254
+ _targetTransport = new WeakMap();
255
+ _traceVariants = new Map();
256
+ _queuedCompiles = [];
257
+ _activeCompiles = 0;
258
+ _traceModules = new Map();
259
+ _requestedVariant;
260
+ _activeVariant;
261
+ _activeInputs;
262
+ _awaitingRetirement;
263
+ _resourceError;
264
+ _aovPipelinesFailed = false;
231
265
  _hasTransmission = false;
232
266
  _hasShadowCatcher = false;
233
267
  _hasDisplacement = false;
@@ -238,23 +272,26 @@ class WGPURayTracePass {
238
272
  _clamping;
239
273
  _transmissiveAlphaMin = 1;
240
274
  _viewProjInv = mat4.create();
275
+ _projectionInv = mat4.create();
241
276
  _cameraWorld = mat4.create();
242
277
  _apertureRadius = 0;
243
278
  _focusDistance = 10;
244
279
  _needsUpdateBindGroups = true;
245
- _computeShaderModule;
280
+ // Photon maps are per target. Switching targets only rebinds them, which
281
+ // must not send steady frames through the asynchronous pipeline path.
282
+ _needsPhotonTargetBindGroups = false;
283
+ // Target selection is not a scene-resource edit. A stale fallback must not
284
+ // leave global bindings dirty after switching back to a prepared target.
285
+ _needsUpdateOutputBindGroup = false;
246
286
  _aovPipelines = new WeakMap();
247
287
  _depthCompute;
248
288
  _depthTracePipeline;
249
289
  _depthPipeline;
250
290
  _shaderCache;
251
- _debugBVHBuffer;
252
- _isCompilingShader = false;
253
- _shaderCompilationDiagnostics;
254
- _pipelineError;
291
+ _bvhCPU;
292
+ _bvhDataBytes = 0;
255
293
  // Fold scene invariants before backend inlining; runtime edits use uniforms.
256
294
  _bakePipelineConstants = true;
257
- _failedPipelineSignature;
258
295
  constructor(device, _textureSelection = { requested: 'atlas' }) {
259
296
  this._textureSelection = _textureSelection;
260
297
  this._device = device;
@@ -262,7 +299,7 @@ class WGPURayTracePass {
262
299
  if (_textureSelection.support)
263
300
  this._bindless = new WGPUBindlessTextures(device);
264
301
  this._uniforms = {
265
- cameraUniform: this._uniformBuffer(36 * 4),
302
+ cameraUniform: this._uniformBuffer(52 * 4),
266
303
  rtUniform: this._uniformBuffer(this._rtUniform.buffer.byteLength),
267
304
  analyticLights: this._uniformBuffer(FIXED_LIGHT_COUNT * PIXELS_PER_LIGHT * 4 * 4)
268
305
  };
@@ -298,8 +335,14 @@ class WGPURayTracePass {
298
335
  device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, new Float32Array([Number.MAX_SAFE_INTEGER]), { bytesPerRow: 4 }, [1, 1]);
299
336
  }
300
337
  getOutputBuffer() {
338
+ // Another render may have traced since this target's samples were written.
339
+ const transport = this._targetTransport.get(this._activeTarget) ?? 0;
301
340
  return {
302
341
  color: this._outColorBuffer,
342
+ ...(transport & 2 || ((this._transport?.debugGroup ?? -1) >= 0 || causticDiagnostics.debugGroup >= 0) ? { wideHdr: true } : {}),
343
+ ...(transport & 1 && this._causticLight?.get(this._activeTarget)
344
+ ? { caustic: this._causticLight.get(this._activeTarget) }
345
+ : {}),
303
346
  normal: this._outNormalBuffer,
304
347
  albedo: this._outAlbedoBuffer,
305
348
  shadowCatcher: this._outShadowCatcherBuffer
@@ -494,16 +537,33 @@ class WGPURayTracePass {
494
537
  varianceBuffer.destroy();
495
538
  }
496
539
  }
497
- _createPipelineLayout() {
540
+ _createPipelineLayout(bindless, photonMapping) {
498
541
  const descriptor = {
499
542
  label: 'Ray Trace Pipeline Layout',
500
- bindGroupLayouts: (this._bindless ? bindlessBindGroupLayouts() : rayTraceBindGroupLayouts()).map(layout => this._shaderCache.createBindGroupLayout(layout))
543
+ bindGroupLayouts: (bindless ? bindlessBindGroupLayouts() : rayTraceBindGroupLayouts()).map((layout, index) => {
544
+ if (photonMapping && index === (bindless ? 0 : 1))
545
+ layout = {
546
+ ...layout,
547
+ entries: [
548
+ ...layout.entries,
549
+ ...[11, 12].map((binding) => ({
550
+ binding: binding + (bindless ? 4 : 0),
551
+ visibility: GPUShaderStage.COMPUTE,
552
+ texture: { sampleType: 'uint' }
553
+ }))
554
+ ]
555
+ };
556
+ return this._shaderCache.createBindGroupLayout(layout);
557
+ })
501
558
  };
502
- this._rayTracePipelineLayout = this._shaderCache.createPipelineLayout(this._bindless ? resourceTableLayout(descriptor) : descriptor);
559
+ return this._shaderCache.createPipelineLayout(bindless ? resourceTableLayout(descriptor) : descriptor);
503
560
  }
504
561
  _updateGPUBuffer(label, gpuBuffer, data, minSize = WGPU_SCENE_BUFFER_MIN_BYTES) {
505
562
  const device = this._device;
506
563
  const size = Math.max(data.byteLength, minSize);
564
+ const limit = Math.min(device.limits?.maxBufferSize ?? Infinity, device.limits?.maxStorageBufferBindingSize ?? Infinity);
565
+ if (size > limit)
566
+ throw new RangeError(`${label} buffer requires ${size} bytes; device storage buffer limit is ${limit} bytes.`);
507
567
  if (gpuBuffer == null || gpuBuffer.size < size) {
508
568
  this._needsUpdateBindGroups = true;
509
569
  if (gpuBuffer) {
@@ -514,10 +574,22 @@ class WGPURayTracePass {
514
574
  size,
515
575
  usage: GPUBufferUsage.STORAGE |
516
576
  GPUBufferUsage.COPY_DST |
517
- GPUBufferUsage.COPY_SRC
577
+ GPUBufferUsage.COPY_SRC,
578
+ // Chromium's mapped-creation IPC payload is limited to a signed 32-bit
579
+ // size even when the adapter supports larger storage buffers.
580
+ mappedAtCreation: size < 0x80000000
518
581
  });
582
+ if (size < 0x80000000) {
583
+ new Uint8Array(gpuBuffer.getMappedRange()).set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
584
+ gpuBuffer.unmap();
585
+ return gpuBuffer;
586
+ }
587
+ }
588
+ // Bound each staging allocation/IPC payload for large or reused buffers.
589
+ const chunkBytes = 64 * 1024 * 1024;
590
+ for (let offset = 0; offset < data.byteLength; offset += chunkBytes) {
591
+ this._device.queue.writeBuffer(gpuBuffer, offset, data.buffer, data.byteOffset + offset, Math.min(chunkBytes, data.byteLength - offset));
519
592
  }
520
- this._device.queue.writeBuffer(gpuBuffer, 0, data.buffer);
521
593
  return gpuBuffer;
522
594
  }
523
595
  _uniformBuffer(size) {
@@ -527,22 +599,46 @@ class WGPURayTracePass {
527
599
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
528
600
  });
529
601
  }
530
- async updatePipeline() {
531
- if (this._isCompilingShader) {
602
+ _preparePhotonTarget() {
603
+ if (!this._photonMapping)
532
604
  return;
605
+ this._photonMap ??= new WGPUPhotonMap(this._device);
606
+ const before = this._photonMap.get(this._activeTarget)?.data;
607
+ const requested = this._causticSettings?.photonMapping;
608
+ const n = typeof requested === 'object' ? requested.photonsPerSample : undefined;
609
+ const after = this._photonMap.ensure(this._activeTarget, Number.isFinite(n) && n > 0 ? n : defaultPhotonMapPhotons(this._width, this._height, causticDiagnostics.photonCountDivisor));
610
+ if (before !== after.data)
611
+ this._needsUpdateBindGroups = true;
612
+ }
613
+ /**
614
+ * Prepares the pipeline for the settings currently written to this pass.
615
+ * Everything except compilation runs synchronously, and compilation only
616
+ * writes to its own variant. Renders with different settings can therefore
617
+ * interleave without invalidating each other. Returns a promise while the
618
+ * requested state waits on the GPU; undefined means nothing is outstanding.
619
+ */
620
+ updatePipeline() {
621
+ if (this._disposed)
622
+ return;
623
+ if (this._needsUpdateMeshData || this._needsUpdateMeshMembership || this._pendingGeometryUpdates.size ||
624
+ this._needsRefitTLAS || this._dirtyColorMeshes.size || this._pendingMaterialUpdate || this._needsUpdateLightSamplingData) {
625
+ // A failure can have the same shader signature as its repaired scene.
626
+ // Retry after authored updates, while keeping an unchanged failure idle.
627
+ this._resourceError = undefined;
628
+ for (const [signature, variant] of this._traceVariants)
629
+ if (variant.status === 'failed')
630
+ this._traceVariants.delete(signature);
631
+ if (this._aovPipelinesFailed) {
632
+ this._aovPipelines = new WeakMap();
633
+ this._aovPipelinesFailed = false;
634
+ }
533
635
  }
534
- // Set this before any synchronous preparation. updatePipeline is called
535
- // without awaiting it, so a preparation error must not let createPass run
536
- // with an undefined pipeline in the same frame.
537
- this._isCompilingShader = true;
538
- let attemptedPipelineSignature;
539
- let compilationStartedAt;
540
- let pipelineCompilationStartedAt;
636
+ this._awaitingRetirement = this._bindless?.retirement;
637
+ if (this._awaitingRetirement)
638
+ return this._awaitingRetirement;
639
+ if (this._resourceError)
640
+ return;
541
641
  try {
542
- if (this._bindless?.retirement)
543
- await this._bindless.retirement;
544
- if (this._disposed)
545
- return;
546
642
  if (this._needsUpdateMeshData) {
547
643
  this._updateMeshData();
548
644
  }
@@ -557,29 +653,51 @@ class WGPURayTracePass {
557
653
  this._updateMeshColors();
558
654
  if (this._pendingMaterialUpdate)
559
655
  this._updateMaterialData();
656
+ this._setCaustics(this._causticSettings);
560
657
  if (this._needsUpdateLightSamplingData) {
561
658
  this._updateLightSamplingData();
562
659
  }
563
- const device = this._device;
564
- const pipelineInputs = JSON.stringify([this._constants, this._hasDisplacement,
565
- this._alphaBlendStrategy, this._blasLayoutMask, this._bakePipelineConstants]);
660
+ this._setCaustics(this._causticSettings);
661
+ if (this._lightTracing || this._photonMapping)
662
+ this._causticBounds = causticCasterSphere(this._meshes);
663
+ this._targetTransport.set(this._activeTarget, (+this._lightTracing) | (+this._photonMapping << 1));
664
+ this._preparePhotonTarget();
665
+ const pipelineInputs = JSON.stringify([
666
+ this._constants,
667
+ Boolean(this._constants.SPECTRAL) && this._shareSpectralPrefix,
668
+ this._hasDisplacement,
669
+ this._alphaBlendStrategy,
670
+ this._blasLayoutMask,
671
+ this._bakePipelineConstants,
672
+ this._causticKey,
673
+ !!this._bindless
674
+ ]);
566
675
  // Steady rendering must not repeat capability selection and cache-key
567
676
  // preparation. Dirty resources still take the normal bind-group path.
568
- if (pipelineInputs === this._prevPipelineInputs && this._rayTracePipeline &&
569
- this._rayTraceBindGroups && !this._needsUpdateBindGroups)
677
+ if (pipelineInputs === this._activeInputs &&
678
+ this._rayTracePipeline &&
679
+ this._rayTraceBindGroups &&
680
+ !this._needsUpdateBindGroups) {
681
+ if (this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
682
+ this._needsPhotonTargetBindGroups = false;
683
+ this._needsUpdateOutputBindGroup = false;
684
+ this._updateBindGroups();
685
+ }
686
+ this._requestedVariant = this._activeVariant;
570
687
  return;
688
+ }
571
689
  this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(this._constants);
572
690
  const requiredCapabilities = retainedShaderCapabilities(this._constants);
573
691
  const constants = { ...this._constants, ...requiredCapabilities };
574
- const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) &&
575
- this._alphaBlendStrategy === 'deterministic-source-over';
692
+ const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) && this._alphaBlendStrategy === 'deterministic-source-over';
576
693
  const hasSceneAlpha = Boolean(constants.HAS_ALPHA);
577
694
  const pipelineConstantsKeys = wgpuPipelineConstantKeys(constants);
578
- const defineCode = WGPU_SHADER_DEFINE_KEYS
579
- .map((key) => `const ${key} = ${constants[key]};`)
580
- .join('\n');
695
+ const defineCode = WGPU_SHADER_DEFINE_KEYS.map((key) => `const ${key} = ${constants[key]};`).join('\n');
581
696
  const structure = {
697
+ shareSpectralPrefix: Boolean(constants.SPECTRAL) && this._shareSpectralPrefix,
582
698
  hasDisplacement: this._hasDisplacement,
699
+ caustics: this._causticKey,
700
+ bindless: !!this._bindless,
583
701
  hasAlpha,
584
702
  hasSceneAlpha,
585
703
  hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
@@ -588,10 +706,11 @@ class WGPURayTracePass {
588
706
  bakePipelineConstants: this._bakePipelineConstants
589
707
  };
590
708
  const structureKey = JSON.stringify({
591
- ...structure, defineCode,
709
+ ...structure,
710
+ defineCode,
592
711
  constants: pipelineConstantsKeys
593
- .filter(key => !(key in requiredCapabilities))
594
- .map(key => [key, constants[key]])
712
+ .filter((key) => !(key in requiredCapabilities))
713
+ .map((key) => [key, constants[key]])
595
714
  });
596
715
  // Prefer an existing compatible pipeline, including an earlier lean
597
716
  // variant. Only grow the current variant when it cannot cover an edit;
@@ -599,280 +718,279 @@ class WGPURayTracePass {
599
718
  const capabilities = this._shaderCapabilities.find(structureKey, requiredCapabilities) ??
600
719
  retainedShaderCapabilities(this._constants, structureKey === this._retainedShaderStructure ? this._retainedShaderCapabilities : undefined);
601
720
  Object.assign(constants, capabilities);
602
- const pipelineSignature = JSON.stringify({
721
+ // Runtime feature values live in uniforms, so edits can share a pipeline
722
+ // as long as its compiled defines and overrides are the same.
723
+ const signature = JSON.stringify({
603
724
  ...structure,
604
- constants: Object.keys(constants).sort().map(key => [key, constants[key]])
725
+ defineCode,
726
+ constants: pipelineConstantsKeys.map((key) => [key, constants[key]])
605
727
  });
606
- attemptedPipelineSignature = pipelineSignature;
607
- if (pipelineSignature === this._failedPipelineSignature) {
728
+ const variant = this._traceVariants.get(signature) ??
729
+ this._startTraceVariant(signature, constants, { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys, structureKey, capabilities });
730
+ this._requestedVariant = variant;
731
+ if (variant.status === 'pending') {
732
+ this._requestQueuedCompile(variant);
733
+ return variant.settled;
734
+ }
735
+ if (variant.status === 'failed')
608
736
  return;
737
+ this._installTraceVariant(variant, pipelineInputs);
738
+ }
739
+ catch (error) {
740
+ this._resourceError = error;
741
+ this._needsUpdateBindGroups = true;
742
+ console.error('Failed to update WebGPU ray tracing resources.', error);
743
+ }
744
+ }
745
+ /** Runs only for a ready variant, without yielding, so tracing can start in this call. */
746
+ _installTraceVariant(variant, pipelineInputs) {
747
+ if (this._activeVariant !== variant) {
748
+ this._activeVariant = variant;
749
+ this._rayTracePipeline = variant.pipeline;
750
+ this._depthCompute = variant.depthCompute;
751
+ this._needsUpdateBindGroups = true;
752
+ }
753
+ const emission = () => causticEmissionTable(this._analyticLights, this._emissiveTriangles, this._causticBounds[3], this._envImageSource && 'sky' in this._envImageSource
754
+ ? proceduralEnvironmentPower(this._envImageSource)
755
+ : estimateEnvironmentPower(this._envImageSource));
756
+ const casters = () => causticDiagnostics.aggregateCasters ? [this._causticBounds] : causticCasterSpheres(this._meshes);
757
+ if (this._lightTracing && this._causticLight) {
758
+ this._causticLight.setDistribution(casters(), emission());
759
+ this._causticLight.use(variant.lightEmitter);
760
+ }
761
+ if (this._photonMapping && this._photonMap) {
762
+ this._photonMap.emission.setDistribution(casters(), emission());
763
+ this._photonMap.emission.use(variant.photonEmitter);
764
+ }
765
+ // TODO rebind when lights, materials, bvh changed.
766
+ if (this._needsUpdateBindGroups || this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
767
+ this._needsUpdateBindGroups = false;
768
+ this._needsPhotonTargetBindGroups = false;
769
+ this._needsUpdateOutputBindGroup = false;
770
+ this._updateBindGroups();
771
+ }
772
+ this._retainedShaderCapabilities = variant.capabilities;
773
+ this._retainedShaderStructure = variant.structureKey;
774
+ this._shaderCapabilities.remember(variant.structureKey, variant.capabilities);
775
+ this._activeInputs = pipelineInputs;
776
+ }
777
+ /** Snapshots everything compilation needs. The task must not read mutable pass state afterwards. */
778
+ _startTraceVariant(signature, constants, shader) {
779
+ const shareSpectralPrefix = Boolean(constants.SPECTRAL) && this._shareSpectralPrefix;
780
+ const bake = this._bakePipelineConstants, bindless = this._bindless;
781
+ const hasDisplacement = this._hasDisplacement, blasLayoutMask = this._blasLayoutMask;
782
+ const lightTracing = this._lightTracing, photonMapping = this._photonMapping;
783
+ const causticKey = this._causticKey, transport = this._transport;
784
+ const { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys } = shader;
785
+ const emitterSource = (mode) => {
786
+ const code = causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode, '', causticDiagnostics.fullEmissionRandom !== false, true, true);
787
+ return bindless ? bindlessShaderSource(code, this._textureSelection.support.enable) : code;
788
+ };
789
+ const lightEmitter = lightTracing ? this._causticLight ??= new WGPUCausticLightPass(this._device) : undefined;
790
+ const photonEmitter = photonMapping ? this._photonMap?.emission : undefined;
791
+ const pipelineDiagnostic = {
792
+ label: 'Ray Trace', entryPoint: 'main', firstFrameRequired: true, status: 'compiling',
793
+ startedAtMs: 0, concurrentCompilationCount: 1
794
+ };
795
+ const variant = {
796
+ status: 'pending', capabilities: shader.capabilities, structureKey: shader.structureKey,
797
+ settled: undefined,
798
+ diagnostics: {
799
+ status: 'compiling', sourceBytes: 0, sourceLines: 0, pipelineCount: 1,
800
+ completedPipelineCount: 0, sourceGenerationTimeMs: 0, moduleCreationTimeMs: 0,
801
+ modules: [], pipelines: [{ ...pipelineDiagnostic }], maximumConcurrentPipelineCount: 1, peakConcurrentPipelineCount: 1
609
802
  }
610
- this._pipelineError = undefined;
611
- const previousPipelineConstantsKeys = wgpuPipelineConstantKeys(this._prevPipelineConstants);
612
- const needsCreateShaderModule = !this._rayTracePipelineLayout ||
613
- !this._computeShaderModule ||
614
- pipelineConstantsKeys.join(',') !==
615
- previousPipelineConstantsKeys.join(',') ||
616
- defineCode !== this._prevDefinesCode ||
617
- hasAlpha !== this._compiledHasAlpha ||
618
- hasSceneAlpha !== this._compiledSceneHasAlpha ||
619
- Boolean(constants.USE_ANISOTROPY_DIRECTIONS) !== this._compiledAnisotropyDirections ||
620
- Boolean(constants.USE_COAT_NORMAL_FRAMES) !== this._compiledCoatNormals ||
621
- this._blasLayoutMask !== this._compiledBlasLayoutMask ||
622
- (this._bakePipelineConstants &&
623
- pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]));
624
- const needsCreatePipeline = needsCreateShaderModule ||
625
- pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]);
626
- compilationStartedAt = needsCreatePipeline
627
- ? shaderCompilationNow()
628
- : undefined;
629
- let sourceGenerationTimeMs = 0;
630
- let moduleCreationTimeMs = 0;
631
- let sourceBytes = this._shaderCompilationDiagnostics?.sourceBytes ?? 0;
632
- let sourceLines = this._shaderCompilationDiagnostics?.sourceLines ?? 0;
633
- let sourceFingerprint = this._shaderCompilationDiagnostics?.modules[0]?.sourceFingerprint ?? '';
634
- if (needsCreateShaderModule) {
635
- const sourceGenerationStartedAt = shaderCompilationNow();
636
- let rayTraceShaderCode = wgpuShaderSource(constants, {
637
- hasDisplacement: this._hasDisplacement, hasAlpha, hasSceneAlpha,
803
+ };
804
+ const device = this._device, groupIndices = bindless ? [0, 1] : [0, 1, 2];
805
+ let startedAt = 0;
806
+ const compile = async () => {
807
+ startedAt = shaderCompilationNow();
808
+ // Override values are not part of the WGSL unless they are baked, so a
809
+ // quality edit can share the module that the driver has already parsed.
810
+ const moduleKey = JSON.stringify([causticKey, !!bindless, shareSpectralPrefix, Boolean(constants.SPECTRAL), hasAlpha, hasSceneAlpha, hasDisplacement,
811
+ constants.USE_ANISOTROPY_DIRECTIONS, constants.USE_COAT_NORMAL_FRAMES, blasLayoutMask, defineCode, pipelineConstantsKeys,
812
+ bake && pipelineConstantsKeys.map((key) => constants[key])]);
813
+ let source = this._traceModules.get(moduleKey);
814
+ if (!source) {
815
+ let code = wgpuShaderSource(constants, {
816
+ maxPathEvents: MAX_PATH_EVENTS_WGPU,
817
+ hasDisplacement,
818
+ channelIndependentRis: Boolean(constants.SPECTRAL),
819
+ shareSpectralPrefix,
820
+ hasAlpha,
821
+ hasSceneAlpha,
822
+ ...transport?.features,
823
+ lightTracing,
824
+ photonMapping,
825
+ photonMaxDepth: causticDiagnostics.photonMaxDepth ?? 20,
826
+ photonDirectSubset: causticDiagnostics.photonDirectSubset,
827
+ debugGroup: causticDiagnostics.debugGroup,
828
+ workgroupSize: transport?.workgroupSize ?? WORKGROUP_SIZE,
638
829
  hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
639
830
  hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
640
- blasLayoutMask: this._blasLayoutMask,
641
- dynamicEnvironment: true
642
- }, this._bakePipelineConstants);
643
- if (this._bindless)
644
- rayTraceShaderCode = bindlessShaderSource(rayTraceShaderCode, this._textureSelection.support.enable);
645
- sourceBytes = rayTraceShaderCode.length;
646
- sourceLines = shaderSourceLineCount(rayTraceShaderCode);
647
- sourceFingerprint = shaderSourceFingerprint(rayTraceShaderCode);
648
- sourceGenerationTimeMs =
649
- shaderCompilationNow() - sourceGenerationStartedAt;
650
- const moduleCreationStartedAt = shaderCompilationNow();
651
- this._computeShaderModule = this._shaderCache.createShaderModule({
652
- label: 'Ray Trace',
653
- code: rayTraceShaderCode
654
- });
655
- moduleCreationTimeMs =
656
- shaderCompilationNow() - moduleCreationStartedAt;
657
- this._compiledHasAlpha = hasAlpha;
658
- this._compiledSceneHasAlpha = hasSceneAlpha;
659
- this._compiledAnisotropyDirections = Boolean(constants.USE_ANISOTROPY_DIRECTIONS);
660
- this._compiledCoatNormals = Boolean(constants.USE_COAT_NORMAL_FRAMES);
661
- this._compiledBlasLayoutMask = this._blasLayoutMask;
831
+ blasLayoutMask,
832
+ cameraProjectionInverse: true,
833
+ perInstanceBounds: true,
834
+ compactVertices: true,
835
+ dynamicEnvironment: true,
836
+ displayAovStorage: true
837
+ }, bake);
838
+ if (bindless)
839
+ code = bindlessShaderSource(code, this._textureSelection.support.enable);
840
+ variant.diagnostics.sourceGenerationTimeMs = shaderCompilationNow() - startedAt;
841
+ const moduleStartedAt = shaderCompilationNow();
842
+ source = {
843
+ module: this._shaderCache.createShaderModule({ label: 'Ray Trace', code }),
844
+ bytes: code.length, lines: shaderSourceLineCount(code), fingerprint: shaderSourceFingerprint(code)
845
+ };
846
+ variant.diagnostics.moduleCreationTimeMs = shaderCompilationNow() - moduleStartedAt;
847
+ this._traceModules.set(moduleKey, source);
848
+ if (this._traceModules.size > 8)
849
+ this._traceModules.delete(this._traceModules.keys().next().value);
662
850
  }
663
- if (needsCreatePipeline) {
664
- if (!this._rayTracePipelineLayout) {
665
- device.pushErrorScope('validation');
666
- let layoutScopeOpen = true;
667
- try {
668
- this._createPipelineLayout();
669
- const layoutError = await device.popErrorScope();
670
- layoutScopeOpen = false;
671
- if (layoutError)
672
- throw layoutError;
673
- }
674
- catch (error) {
675
- const scopedError = layoutScopeOpen
676
- ? await device.popErrorScope().catch(() => null)
677
- : null;
678
- this._rayTracePipelineLayout = undefined;
679
- const detail = joinWebGPUErrors(error, scopedError);
680
- throw new Error(`Unable to create the ray tracing pipeline layout: ${detail}`);
681
- }
682
- }
683
- // NOTE must before creating pipeline.
684
- // Because createComputePipelineAsync is async.
685
- // The constants may be changed after the pipeline is created(in setMesh).
686
- this._prevPipelineConstants = { ...constants };
687
- this._prevDefinesCode = defineCode;
688
- this._needsUpdateBindGroups = true;
689
- device.pushErrorScope('validation');
690
- let pipelineScopeOpen = true;
691
- try {
692
- const pipelineConstants = !this._bakePipelineConstants
693
- ? pipelineConstantsKeys.reduce((obj, key) => {
694
- obj[key] = +constants[key];
695
- return obj;
696
- }, {})
697
- : undefined;
698
- const diagnostic = {
699
- label: 'Ray Trace',
700
- entryPoint: 'main',
701
- firstFrameRequired: true,
702
- status: 'compiling'
703
- };
704
- pipelineCompilationStartedAt = shaderCompilationNow();
705
- diagnostic.startedAtMs = 0;
706
- diagnostic.concurrentCompilationCount = 1;
707
- this._shaderCompilationDiagnostics = {
708
- status: 'compiling',
709
- sourceBytes,
710
- sourceLines,
711
- pipelineCount: 1,
712
- completedPipelineCount: 0,
713
- sourceGenerationTimeMs,
714
- moduleCreationTimeMs,
715
- modules: [
716
- {
717
- label: 'Ray Trace',
718
- sourceBytes,
719
- sourceLines,
720
- sourceFingerprint
721
- }
722
- ],
723
- pipelines: [{ ...diagnostic }],
724
- maximumConcurrentPipelineCount: 1,
725
- peakConcurrentPipelineCount: 1
726
- };
727
- let rayTracePipeline;
728
- try {
729
- rayTracePipeline = await this._shaderCache.createComputePipelineAsync({
730
- label: 'Ray Trace',
731
- layout: this._rayTracePipelineLayout,
732
- compute: {
733
- module: this._computeShaderModule,
734
- entryPoint: 'main',
735
- constants: pipelineConstants
736
- }
737
- });
738
- diagnostic.status = 'ready';
739
- }
740
- catch (error) {
741
- diagnostic.status = 'failed';
742
- diagnostic.error = getWebGPUErrorMessage(error);
743
- // Pipeline creation already reports WGSL failures. Pull the much
744
- // heavier module diagnostics only on that failure path so normal
745
- // startup does not schedule a second full-module inspection.
746
- const info = await this._computeShaderModule
747
- .getCompilationInfo()
748
- .catch(() => undefined);
749
- const shaderErrors = (info?.messages ?? [])
750
- .filter((message) => message.type === 'error');
751
- if (shaderErrors.length) {
752
- throw new Error(shaderErrors
753
- .map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
754
- .join('\n'));
755
- }
756
- throw error;
757
- }
758
- finally {
759
- diagnostic.endedAtMs =
760
- shaderCompilationNow() - pipelineCompilationStartedAt;
761
- diagnostic.durationMs = diagnostic.endedAtMs;
762
- }
763
- this._rayTracePipeline = rayTracePipeline;
764
- this._depthCompute = { module: this._computeShaderModule, constants: pipelineConstants };
765
- const pipelineError = await device.popErrorScope();
766
- pipelineScopeOpen = false;
767
- if (pipelineError)
768
- throw pipelineError;
769
- const compilationCompletedAt = shaderCompilationNow();
770
- this._shaderCompilationDiagnostics = {
771
- ...this._shaderCompilationDiagnostics,
772
- status: 'ready',
773
- completedPipelineCount: 1,
774
- pipelines: [{ ...diagnostic }],
775
- pipelineCompilationTimeMs: compilationCompletedAt - pipelineCompilationStartedAt,
776
- totalTimeMs: compilationCompletedAt - compilationStartedAt
777
- };
778
- }
779
- catch (error) {
780
- const scopedError = pipelineScopeOpen
781
- ? await device.popErrorScope().catch(() => null)
782
- : null;
783
- const detail = joinWebGPUErrors(error, scopedError);
784
- if (!this._bakePipelineConstants &&
785
- isWebGPUPipelineSpecializationFailure(error, scopedError)) {
786
- const failedAt = shaderCompilationNow();
787
- if (this._shaderCompilationDiagnostics?.status === 'compiling') {
788
- this._shaderCompilationDiagnostics = {
789
- ...this._shaderCompilationDiagnostics,
790
- status: 'failed',
791
- pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
792
- ? undefined
793
- : failedAt - pipelineCompilationStartedAt,
794
- totalTimeMs: compilationStartedAt == null
795
- ? undefined
796
- : failedAt - compilationStartedAt,
797
- error: detail
798
- };
799
- }
800
- // WebKit can accept a large WGSL module but fail while translating
801
- // its specialization-constant variant to Metal. Baking the values
802
- // into WGSL lets its front end eliminate disabled feature branches
803
- // before creating the Metal library.
804
- this._bakePipelineConstants = true;
805
- this._rayTracePipeline = undefined;
806
- this._rayTraceBindGroups = undefined;
807
- this._computeShaderModule = undefined;
808
- this._prevPipelineConstants = {};
809
- console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
810
- return;
811
- }
812
- throw new Error(detail);
851
+ Object.assign(variant.diagnostics, {
852
+ sourceBytes: source.bytes, sourceLines: source.lines,
853
+ modules: [{ label: 'Ray Trace', sourceBytes: source.bytes, sourceLines: source.lines, sourceFingerprint: source.fingerprint }]
854
+ });
855
+ const { module } = source;
856
+ const layout = this._createPipelineLayout(!!bindless, photonMapping);
857
+ const pipelineConstants = !bake
858
+ ? pipelineConstantsKeys.reduce((obj, key) => {
859
+ obj[key] = +constants[key];
860
+ return obj;
861
+ }, {})
862
+ : undefined;
863
+ const lightSource = lightEmitter && emitterSource('lightTracing');
864
+ const photonSource = photonEmitter && emitterSource('photonMapping');
865
+ // Scopes capture all device errors while open, so one must never span an
866
+ // await: frames presented meanwhile would be blamed on this pipeline.
867
+ device.pushErrorScope('validation');
868
+ const request = this._shaderCache.createComputePipelineAsync({
869
+ label: 'Ray Trace', layout,
870
+ compute: { module, entryPoint: 'main', constants: pipelineConstants }
871
+ });
872
+ const scope = device.popErrorScope().catch(() => null);
873
+ let pipeline;
874
+ try {
875
+ pipeline = await request;
876
+ const scopedError = await scope;
877
+ if (scopedError)
878
+ throw scopedError;
879
+ }
880
+ catch (error) {
881
+ const scopedError = await scope;
882
+ // Pipeline creation already reports WGSL failures. Pull the much
883
+ // heavier module diagnostics only on that failure path so normal
884
+ // startup does not schedule a second full-module inspection.
885
+ const info = await module.getCompilationInfo().catch(() => undefined);
886
+ const shaderErrors = (info?.messages ?? []).filter((message) => message.type === 'error');
887
+ if (shaderErrors.length) {
888
+ throw new Error(shaderErrors
889
+ .map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
890
+ .join('\n'));
813
891
  }
892
+ if (isWebGPUPipelineSpecializationFailure(error, scopedError))
893
+ throw error;
894
+ throw new Error(joinWebGPUErrors(error, scopedError));
814
895
  }
815
- // Compilation can complete after Engine disposal/device loss has released
816
- // the material table and its textures. Never bind that retired generation.
896
+ const groups = lightEmitter || photonEmitter ? groupIndices.map((i) => pipeline.getBindGroupLayout(i)) : [];
897
+ const [light, photon] = await Promise.all([
898
+ lightEmitter?.prepare(lightSource, groups, !!bindless),
899
+ photonEmitter?.prepare(photonSource, groups, !!bindless)
900
+ ]);
901
+ Object.assign(variant, { pipeline, lightEmitter: light, photonEmitter: photon,
902
+ depthCompute: { module, constants: pipelineConstants } });
903
+ };
904
+ const finishDiagnostics = (status, error) => {
905
+ const totalTimeMs = shaderCompilationNow() - startedAt;
906
+ Object.assign(variant.diagnostics, {
907
+ status, completedPipelineCount: status === 'ready' ? 1 : 0, error,
908
+ pipelines: [{ ...pipelineDiagnostic, status, error, endedAtMs: totalTimeMs, durationMs: totalTimeMs }],
909
+ pipelineCompilationTimeMs: totalTimeMs, totalTimeMs
910
+ });
911
+ };
912
+ const run = () => compile().then(() => {
913
+ variant.status = 'ready';
914
+ finishDiagnostics('ready');
915
+ }, (error) => {
916
+ finishDiagnostics('failed', getWebGPUErrorMessage(error));
817
917
  if (this._disposed)
818
918
  return;
819
- // TODO rebind when lights, materials, bvh changed.
820
- if (this._needsUpdateBindGroups) {
821
- this._needsUpdateBindGroups = false;
822
- this._updateBindGroups();
919
+ if (!bake && isWebGPUPipelineSpecializationFailure(error)) {
920
+ // WebKit can accept a large WGSL module but fail while translating
921
+ // its specialization-constant variant to Metal. Baking the values
922
+ // into WGSL lets its front end eliminate disabled feature branches
923
+ // before creating the Metal library. The next request has a new
924
+ // signature, so this one stays pending until it is replaced.
925
+ this._bakePipelineConstants = true;
926
+ this._traceVariants.delete(signature);
927
+ console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
928
+ return;
823
929
  }
824
- this._retainedShaderCapabilities = capabilities;
825
- this._retainedShaderStructure = structureKey;
826
- this._shaderCapabilities.remember(structureKey, capabilities);
827
- this._prevPipelineInputs = pipelineInputs;
828
- this._failedPipelineSignature = undefined;
829
- }
830
- catch (error) {
831
- if (this._disposed)
930
+ // Nobody needs an obsolete result, and a later request should try again.
931
+ if (this._requestedVariant !== variant) {
932
+ this._traceVariants.delete(signature);
933
+ variant.status = 'failed';
832
934
  return;
833
- this._pipelineError = error;
834
- const failedAt = shaderCompilationNow();
835
- if (this._shaderCompilationDiagnostics?.status === 'compiling') {
836
- this._shaderCompilationDiagnostics = {
837
- ...this._shaderCompilationDiagnostics,
838
- status: 'failed',
839
- pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
840
- ? undefined
841
- : failedAt - pipelineCompilationStartedAt,
842
- totalTimeMs: compilationStartedAt == null
843
- ? undefined
844
- : failedAt - compilationStartedAt,
845
- error: getWebGPUErrorMessage(error)
846
- };
847
935
  }
848
- this._failedPipelineSignature = attemptedPipelineSignature;
849
- // Do not allow the render loop to submit the previous/partial state.
850
- // The failed signature prevents retrying the same shader every frame;
851
- // changing a pipeline-affecting setting permits a new attempt.
852
- this._rayTracePipeline = undefined;
853
- this._rayTraceBindGroups = undefined;
854
- this._prevPipelineConstants = {};
855
- this._needsUpdateBindGroups = true;
936
+ variant.status = 'failed';
937
+ variant.error = error;
856
938
  console.error('Failed to update WebGPU ray tracing pipeline.', error);
857
- }
858
- finally {
859
- this._isCompilingShader = false;
939
+ });
940
+ variant.settled = new Promise((resolve) => {
941
+ this._queuedCompiles.push({ variant, signature, run, release: resolve });
942
+ });
943
+ this._traceVariants.set(signature, variant);
944
+ if (this._queuedCompiles.length > MAX_QUEUED_COMPILES) {
945
+ // A dropped variant stays pending, because a render that still wants it
946
+ // replaces it with a new request on its next frame.
947
+ const obsolete = this._queuedCompiles.shift();
948
+ if (this._traceVariants.get(obsolete.signature) === obsolete.variant)
949
+ this._traceVariants.delete(obsolete.signature);
950
+ obsolete.release();
951
+ }
952
+ for (const [key, retained] of this._traceVariants) {
953
+ if (this._traceVariants.size <= MAX_TRACE_VARIANTS)
954
+ break;
955
+ if (retained.status !== 'pending' && retained !== this._activeVariant)
956
+ this._traceVariants.delete(key);
957
+ }
958
+ this._pumpCompiles();
959
+ return variant;
960
+ }
961
+ /** Runs the most recently requested variants first: a dragged slider's last value is the one in use. */
962
+ _pumpCompiles() {
963
+ while (this._activeCompiles < MAX_CONCURRENT_COMPILES && this._queuedCompiles.length) {
964
+ const job = this._queuedCompiles.pop();
965
+ this._activeCompiles++;
966
+ void job.run().finally(() => {
967
+ this._activeCompiles--;
968
+ job.release();
969
+ if (!this._disposed)
970
+ this._pumpCompiles();
971
+ });
860
972
  }
861
973
  }
862
- _prepareAovPipelines(kinds) {
974
+ _requestQueuedCompile(variant) {
975
+ const index = this._queuedCompiles.findIndex((job) => job.variant === variant);
976
+ if (index >= 0)
977
+ this._queuedCompiles.push(...this._queuedCompiles.splice(index, 1));
978
+ }
979
+ _prepareAovPipelines(kinds, compact = false) {
863
980
  const trace = this._rayTracePipeline;
864
981
  if (!trace || !this._depthCompute)
865
982
  return;
866
- const reflectance = (aovMask(kinds) & 24) !== 0;
983
+ const reflectance = (aovMask(kinds) & AOV_REFLECTANCE_MASK) !== 0;
867
984
  let variants = this._aovPipelines.get(trace);
868
985
  if (!variants) {
869
986
  variants = new Map();
870
987
  this._aovPipelines.set(trace, variants);
871
988
  }
872
- let pipelines = variants.get(reflectance);
989
+ const key = Number(reflectance) + Number(compact) * 2;
990
+ let pipelines = variants.get(key);
873
991
  if (!pipelines) {
874
992
  pipelines = {};
875
- variants.set(reflectance, pipelines);
993
+ variants.set(key, pipelines);
876
994
  const pending = pipelines;
877
995
  const textures = this._bindless, groupIndex = textures ? 2 : 3;
878
996
  const output = this._device.createBindGroupLayout({ entries: [
@@ -884,42 +1002,45 @@ class WGPURayTracePass {
884
1002
  ] });
885
1003
  const descriptor = { bindGroupLayouts: Array.from({ length: groupIndex }, (_, i) => trace.getBindGroupLayout(i)).concat(output) };
886
1004
  const layout = this._device.createPipelineLayout(textures ? resourceTableLayout(descriptor) : descriptor);
887
- const compute = { ...this._depthCompute, constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1, AOV_REFLECTANCE: reflectance ? 1 : 0 } };
1005
+ const compute = { ...this._depthCompute, constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1, AOV_REFLECTANCE: reflectance ? 1 : 0, AOV_COMPACT: compact ? 1 : 0 } };
888
1006
  // Keep the UI responsive during first use, and reuse both variants across
889
1007
  // preview/final target changes rather than recreating synchronous pipelines.
890
1008
  void Promise.all([
891
1009
  this._shaderCache.createComputePipelineAsync({ label: 'AOV accumulation', layout, compute: { ...compute, entryPoint: 'aovTrace' } }),
892
1010
  this._shaderCache.createComputePipelineAsync({ label: 'AOV center', layout, compute: { ...compute, entryPoint: 'aovTraceCenter' } })
893
- ]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; });
1011
+ ]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; this._aovPipelinesFailed = true; });
894
1012
  }
895
1013
  if (pipelines.error)
896
1014
  throw pipelines.error;
897
1015
  return pipelines;
898
1016
  }
899
- prepareAovs(kinds) {
900
- const pipelines = this._prepareAovPipelines(kinds);
1017
+ prepareAovs(kinds, compact = false) {
1018
+ const pipelines = this._prepareAovPipelines(kinds, compact);
901
1019
  return !!pipelines?.accumulation && !!pipelines.center;
902
1020
  }
903
- renderAovs(state, camera, samples, cap, kinds) {
1021
+ renderAovs(state, camera, samples, cap, kinds, resolution) {
904
1022
  const groups = this._rayTraceBindGroups;
905
1023
  if (!groups || !this._tlas || !this._preparedMeshData || !kinds.length)
906
1024
  return false;
907
- const pipelines = this._prepareAovPipelines(kinds);
1025
+ const compact = !!resolution && kinds.every(kind => COMPACT_AOV_KINDS.includes(kind));
1026
+ const pipelines = this._prepareAovPipelines(kinds, compact);
908
1027
  if (!pipelines?.accumulation || !pipelines.center)
909
1028
  return false;
910
- state.resize(this._width, this._height);
1029
+ state.resize(resolution?.width ?? this._width, resolution?.height ?? this._height, compact);
911
1030
  const textures = this._bindless, groupIndex = textures ? 2 : 3;
912
1031
  const target = Math.min(samples, cap);
913
1032
  if (state.centerReady && state.samples >= target)
914
1033
  return true;
915
1034
  if (!state.frame) {
916
- const primitiveIds = this._aovPrimitiveCache.get(this._preparedMeshData, this._tlas);
917
- const packed = packAovScene(this._preparedMeshData, this._tlas, camera, this._width, this._height, state.history, primitiveIds, false);
1035
+ // Only the center pass reads primitive IDs and compact display AOVs skip
1036
+ // it; building the map walks every BVH leaf on the main thread.
1037
+ const primitiveIds = compact ? undefined : this._aovPrimitiveCache.get(this._preparedMeshData, this._tlas);
1038
+ const packed = packAovScene(this._preparedMeshData, this._tlas, camera, state.width, state.height, state.history, primitiveIds, false);
918
1039
  state.frame = packed.frame;
919
1040
  state.setScene(packed.words, primitiveIds);
920
1041
  }
921
1042
  this._device.queue.writeBuffer(this._uniforms.rtUniform, 0, this._rtUniform.buffer);
922
- this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
1043
+ this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance, this._projectionInv));
923
1044
  const device = this._device;
924
1045
  const run = (pipeline, sample) => {
925
1046
  // A fresh uniform for each queued dispatch keeps batch samples independent.
@@ -943,11 +1064,12 @@ class WGPURayTracePass {
943
1064
  parameters.destroy();
944
1065
  };
945
1066
  if (!state.centerReady) {
946
- run(pipelines.center, 0);
1067
+ if (!compact)
1068
+ run(pipelines.center, 0);
947
1069
  state.centerReady = true;
948
1070
  state.history.accept(state.frame);
949
1071
  }
950
- if ((aovMask(kinds) & 31) === 0)
1072
+ if ((aovMask(kinds) & AOV_ACCUMULATED_MASK) === 0)
951
1073
  state.samples = target;
952
1074
  while (state.samples < target) {
953
1075
  run(pipelines.accumulation, state.samples);
@@ -955,13 +1077,27 @@ class WGPURayTracePass {
955
1077
  }
956
1078
  return true;
957
1079
  }
958
- async readDepthSamples(area, aspect) {
1080
+ async pickSurface(x, y, aspect) {
1081
+ const source = this._preparedMeshData, tlas = this._tlas;
1082
+ if (!source || !tlas)
1083
+ return;
1084
+ const values = await this._readDepthSamples({ x, y, size: 0 }, aspect, true);
1085
+ if (!values || source !== this._preparedMeshData || tlas !== this._tlas)
1086
+ return;
1087
+ return resolveSurfaceHit(source, tlas, values);
1088
+ }
1089
+ readDepthSamples(area, aspect) {
1090
+ return this._readDepthSamples(area, aspect);
1091
+ }
1092
+ async _readDepthSamples(area, aspect, picking = false) {
959
1093
  const tracePipeline = this._rayTracePipeline;
960
1094
  const groups = this._rayTraceBindGroups;
961
1095
  const textures = this._bindless, textureRevision = textures?.revision;
962
- if (!tracePipeline || !groups || !this._depthCompute || this._isCompilingShader)
1096
+ if (!tracePipeline || !groups || !this._depthCompute || this.isCompilingShader())
963
1097
  return;
964
1098
  const packed = packDepthSampleArea(area, aspect ?? this._width / this._height);
1099
+ if (picking)
1100
+ packed.set([-1, -1], 2);
965
1101
  if (this._depthTracePipeline !== tracePipeline || !this._depthPipeline) {
966
1102
  const outputLayout = this._shaderCache.createBindGroupLayout({
967
1103
  label: 'Depth query output',
@@ -1013,7 +1149,8 @@ class WGPURayTracePass {
1013
1149
  encoder.copyBufferToBuffer(output, 0, readback, 0, bytes);
1014
1150
  device.queue.submit([encoder.finish()]);
1015
1151
  await readback.mapAsync(GPUMapMode.READ);
1016
- return unpackDepthSamples(new Float32Array(readback.getMappedRange()));
1152
+ const rgba = new Float32Array(readback.getMappedRange());
1153
+ return picking ? rgba.slice(0, 4) : unpackDepthSamples(rgba);
1017
1154
  }
1018
1155
  finally {
1019
1156
  if (readback.mapState === 'mapped')
@@ -1023,15 +1160,17 @@ class WGPURayTracePass {
1023
1160
  params.destroy();
1024
1161
  }
1025
1162
  }
1163
+ /** Whether the latest request waits on GPU work that will settle on its own. */
1026
1164
  isCompilingShader() {
1027
- return this._isCompilingShader;
1165
+ return !!this._awaitingRetirement || this._requestedVariant?.status === 'pending';
1028
1166
  }
1029
1167
  isPipelineReady() {
1030
1168
  return ((!!this._rayTracePipeline) &&
1031
1169
  !!this._rayTraceBindGroups);
1032
1170
  }
1171
+ /** Failure of the latest request. A failed variant does not affect other renders. */
1033
1172
  getPipelineError() {
1034
- return this._pipelineError;
1173
+ return this._resourceError ?? this._requestedVariant?.error;
1035
1174
  }
1036
1175
  _updateBindGroups() {
1037
1176
  const { _device } = this;
@@ -1054,14 +1193,18 @@ class WGPURayTracePass {
1054
1193
  }
1055
1194
  }));
1056
1195
  const textureEntries = [
1057
- ...(this._bindless ? [] : [{
1058
- binding: 0,
1059
- resource: this._mapsSampler
1060
- },
1061
- {
1062
- binding: 1,
1063
- resource: this._maps.createView()
1064
- }]),
1196
+ ...(this._bindless
1197
+ ? []
1198
+ : [
1199
+ {
1200
+ binding: 0,
1201
+ resource: this._mapsSampler
1202
+ },
1203
+ {
1204
+ binding: 1,
1205
+ resource: this._maps.createView()
1206
+ }
1207
+ ]),
1065
1208
  {
1066
1209
  binding: 3,
1067
1210
  resource: {
@@ -1071,6 +1214,12 @@ class WGPURayTracePass {
1071
1214
  { binding: 4, resource: this._envTex.createView() },
1072
1215
  { binding: 5, resource: this._envDistributionTex.createView() },
1073
1216
  { binding: 6, resource: this._backgroundTex.createView() },
1217
+ ...(this._photonMapping
1218
+ ? [
1219
+ { binding: 11, resource: this._photonMap.get(this._activeTarget).data.createView() },
1220
+ { binding: 12, resource: this._photonMap.get(this._activeTarget).headers.createView() }
1221
+ ]
1222
+ : []),
1074
1223
  { binding: 8, resource: this._lightSamplingTexture.createView() },
1075
1224
  { binding: 9, resource: this._openPbrEnergyLut.createView() },
1076
1225
  {
@@ -1078,11 +1227,18 @@ class WGPURayTracePass {
1078
1227
  resource: this._adaptiveSamplingTexture.createView()
1079
1228
  }
1080
1229
  ];
1081
- const bufferBindGroup = _device.createBindGroup({ layout: bindGroupPipeline.getBindGroupLayout(0),
1082
- entries: this._bindless ? bufferEntries.concat(textureEntries.map(e => ({ ...e, binding: e.binding + 4 }))) : bufferEntries });
1083
- const texturesBindGroup = this._bindless ? undefined : _device.createBindGroup({
1084
- layout: bindGroupPipeline.getBindGroupLayout(1), entries: textureEntries
1230
+ const bufferBindGroup = _device.createBindGroup({
1231
+ layout: bindGroupPipeline.getBindGroupLayout(0),
1232
+ entries: this._bindless
1233
+ ? bufferEntries.concat(textureEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
1234
+ : bufferEntries
1085
1235
  });
1236
+ const texturesBindGroup = this._bindless
1237
+ ? undefined
1238
+ : _device.createBindGroup({
1239
+ layout: bindGroupPipeline.getBindGroupLayout(1),
1240
+ entries: textureEntries
1241
+ });
1086
1242
  const uniformBindGroup = _device.createBindGroup({
1087
1243
  layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 1 : 2),
1088
1244
  entries: Object.keys(this._uniforms).map((key, i) => ({
@@ -1092,19 +1248,36 @@ class WGPURayTracePass {
1092
1248
  }
1093
1249
  }))
1094
1250
  });
1251
+ if (this._photonMapping) {
1252
+ // Explicit layouts still track unused textures. Bind a placeholder while
1253
+ // emission writes the real packed map through its storage view.
1254
+ const emissionEntries = textureEntries.map((entry) => entry.binding === 11 || entry.binding === 12
1255
+ ? { ...entry, resource: this._photonMap.placeholder.createView() }
1256
+ : entry);
1257
+ const sceneGroup = _device.createBindGroup({
1258
+ layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 0 : 1),
1259
+ entries: this._bindless
1260
+ ? bufferEntries.concat(emissionEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
1261
+ : emissionEntries
1262
+ });
1263
+ this._photonEmissionGroups = this._bindless
1264
+ ? [sceneGroup, uniformBindGroup]
1265
+ : [bufferBindGroup, sceneGroup, uniformBindGroup];
1266
+ }
1095
1267
  const target = this._targetOutputs.get(this._activeTarget);
1096
- const outputBindGroup = target.pipeline === bindGroupPipeline && target.bindGroup || _device.createBindGroup({
1097
- layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 2 : 3),
1098
- entries: [
1099
- this._outColorBuffer,
1100
- this._outNormalBuffer,
1101
- this._outAlbedoBuffer,
1102
- this._outShadowCatcherBuffer
1103
- ].map((buffer, i) => ({
1104
- binding: i,
1105
- resource: { buffer }
1106
- }))
1107
- });
1268
+ const outputBindGroup = (target.pipeline === bindGroupPipeline && target.bindGroup) ||
1269
+ _device.createBindGroup({
1270
+ layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 2 : 3),
1271
+ entries: [
1272
+ this._outColorBuffer,
1273
+ this._outNormalBuffer,
1274
+ this._outAlbedoBuffer,
1275
+ this._outShadowCatcherBuffer
1276
+ ].map((buffer, i) => ({
1277
+ binding: i,
1278
+ resource: { buffer }
1279
+ }))
1280
+ });
1108
1281
  target.bindGroup = outputBindGroup;
1109
1282
  target.pipeline = bindGroupPipeline;
1110
1283
  this._rayTraceBindGroups = [
@@ -1131,6 +1304,36 @@ class WGPURayTracePass {
1131
1304
  encoder.clearBuffer(this._outAlbedoBuffer);
1132
1305
  encoder.clearBuffer(this._outShadowCatcherBuffer);
1133
1306
  }
1307
+ const offset = this._rtUniform.views.scaleOffset;
1308
+ const causticBatch = this._lightTracing && offset[2] === tiles[0] - 1 && offset[3] === tiles[1] - 1;
1309
+ const photonBatch = this._photonMapping && offset[2] === 0 && offset[3] === 0;
1310
+ if (photonBatch && this._photonMap) {
1311
+ const requested = this._causticSettings?.photonMapping, initial = typeof requested === 'object' ? requested.initialRadius : undefined;
1312
+ const radius = Number.isFinite(initial) && initial > 0 ? initial : Math.max((causticDiagnostics.photonPerCasterRadius
1313
+ ? causticCasterSpheres(this._meshes).reduce((maximum, s) => Math.max(maximum, s[3]), 0)
1314
+ : this._causticBounds[3]) * 0.03, 1e-4);
1315
+ const batch = Math.max(1, this._rtUniform.views.sampleCount[0] - this._rtUniform.views.sampleSequenceOffset[0] + 1);
1316
+ const photonViewProjection = mat4.create();
1317
+ mat4.invert(photonViewProjection, this._viewProjInv);
1318
+ this._photonMap.encode(encoder, this._photonEmissionGroups, this._activeTarget, photonRadius(radius, causticDiagnostics.photonDebugBatch ?? batch, causticDiagnostics.photonAlpha ?? 2 / 3,
1319
+ // Allocation limits may reduce the requested count.
1320
+ this._photonMap.get(this._activeTarget).count, causticDiagnostics.photonRadiusScaling), {
1321
+ querySet: timing?.querySet,
1322
+ resourceTable: this._bindless?.table,
1323
+ orthographic: !this._causticPerspective,
1324
+ progressiveBatch: batch,
1325
+ width: this._width,
1326
+ height: this._height,
1327
+ clear,
1328
+ run: true,
1329
+ photons: 1,
1330
+ batch: Math.floor(this._rtUniform.views.sampleCount[0]),
1331
+ ...this._transport?.uniform,
1332
+ viewProjection: photonViewProjection,
1333
+ casterSphere: this._causticBounds,
1334
+ cameraScale: this._causticCameraScale
1335
+ });
1336
+ }
1134
1337
  const descriptor = {};
1135
1338
  const querySet = timing?.querySet;
1136
1339
  if (querySet) {
@@ -1150,16 +1353,40 @@ class WGPURayTracePass {
1150
1353
  this._rayTraceBindGroups.forEach((bindGroup, idx) => {
1151
1354
  pass.setBindGroup(idx, bindGroup);
1152
1355
  });
1153
- pass.dispatchWorkgroups(Math.ceil(dispatchWidth / WORKGROUP_SIZE), Math.ceil(dispatchHeight / WORKGROUP_SIZE), 1);
1356
+ researchTransport()?.capture(this);
1357
+ pass.dispatchWorkgroups(Math.ceil(dispatchWidth / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), Math.ceil(dispatchHeight / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), 1);
1154
1358
  pass.end();
1359
+ if (this._lightTracing && this._causticLight) {
1360
+ const viewProjection = mat4.create();
1361
+ mat4.invert(viewProjection, this._viewProjInv);
1362
+ const requested = this._causticSettings?.lightTracing;
1363
+ const count = typeof requested === 'object' ? requested.photonsPerSample : undefined;
1364
+ const photons = Math.min(this._device.limits.maxComputeWorkgroupsPerDimension * 64, Math.max(1, Math.round(Number.isFinite(count) ? count : (this._width * this._height) / 4)));
1365
+ this._causticLight.encode(encoder, this._rayTraceBindGroups.slice(0, this._bindless ? 2 : 3), this._activeTarget, {
1366
+ querySet: causticBatch ? querySet : undefined,
1367
+ width: this._width,
1368
+ height: this._height,
1369
+ clear,
1370
+ run: !!causticBatch,
1371
+ photons,
1372
+ batch: Math.floor(this._rtUniform.views.sampleCount[0]),
1373
+ ...this._transport?.uniform,
1374
+ resourceTable: this._bindless?.table,
1375
+ orthographic: !this._causticPerspective,
1376
+ viewProjection,
1377
+ casterSphere: this._causticBounds,
1378
+ cameraScale: this._causticCameraScale
1379
+ });
1380
+ }
1381
+ return (causticBatch ? 1 : 0) | (photonBatch ? 2 : 0);
1155
1382
  }
1156
1383
  async getDebugBVHData() {
1157
- if (!this._debugBVHBuffer) {
1158
- throw new Error(`software-bvh exposes no CPU-readable BVH debug buffer`);
1159
- }
1160
- return {
1161
- buffer: this._debugBVHBuffer
1162
- };
1384
+ if (!this._bvhCPU)
1385
+ throw new Error('software-bvh exposes no CPU-readable BVH debug buffer');
1386
+ // Debug capture pays for a combined copy only when explicitly requested.
1387
+ const buffer = new ArrayBuffer(this._bvhDataBytes);
1388
+ this._writeBVH(buffer, this._bvhCPU.tlas, this._bvhCPU.blases);
1389
+ return { buffer };
1163
1390
  }
1164
1391
  getDiagnostics() {
1165
1392
  const atlas = this._materialData instanceof MaterialTable ? this._materialData.textureAtlasStats : undefined;
@@ -1174,7 +1401,7 @@ class WGPURayTracePass {
1174
1401
  scale: atlas?.scale ?? 1, metadataBytes: 0, resourceTableCapacity: 0, retiredBytes: 0
1175
1402
  })
1176
1403
  },
1177
- shaderCompilation: this._shaderCompilationDiagnostics,
1404
+ shaderCompilation: this._requestedVariant?.diagnostics,
1178
1405
  lightSampling: {
1179
1406
  emissiveTriangles: this._emissiveTriangles.length,
1180
1407
  activeChoices: this._rtUniform.views.lightChoiceCount[0],
@@ -1194,22 +1421,13 @@ class WGPURayTracePass {
1194
1421
  if (next === this._alphaBlendStrategy)
1195
1422
  return false;
1196
1423
  this._alphaBlendStrategy = next;
1197
- if (this._constants.HAS_ALPHA_BLEND) {
1198
- this._computeShaderModule = undefined;
1199
- this._rayTracePipeline = undefined;
1200
- this._rayTracePipelineLayout = undefined;
1201
- this._rayTraceBindGroups = undefined;
1202
- this._prevPipelineConstants = {};
1203
- this._failedPipelineSignature = undefined;
1204
- this._needsUpdateBindGroups = true;
1205
- }
1206
1424
  return true;
1207
1425
  }
1208
1426
  hasDepthOfField() {
1209
1427
  return this._apertureRadius > 0;
1210
1428
  }
1211
1429
  setBounce(bounce) {
1212
- const next = normalizeBounces(bounce);
1430
+ const next = normalizeBounces(bounce, MAX_PATH_EVENTS_WGPU);
1213
1431
  const changed = this._bounces !== next;
1214
1432
  this._bounces = next;
1215
1433
  this._rtUniform.views.bounces[0] = next;
@@ -1265,8 +1483,30 @@ class WGPURayTracePass {
1265
1483
  this._ensureAdaptiveSamplingTexture(width, height);
1266
1484
  this._device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, data, { bytesPerRow: width * Float32Array.BYTES_PER_ELEMENT }, [width, height]);
1267
1485
  }
1486
+ _shareSpectralPrefix = true;
1268
1487
  setQuality(options) {
1269
- writeTraceQuality(this._rtUniform.views, options);
1488
+ this._shareSpectralPrefix = options.dispersionWavelengths !== 1;
1489
+ WGPURayTracePass.prototype._setCaustics.call(this, options.caustics);
1490
+ writeTraceQuality(this._rtUniform.views, options, MAX_PATH_EVENTS_WGPU);
1491
+ }
1492
+ _setCaustics(settings) {
1493
+ this._causticSettings = settings;
1494
+ const transport = researchTransport()?.configure(this, 'webgpu');
1495
+ if (!!transport?.enabled !== !!this._transport?.enabled)
1496
+ this._needsUpdateLightSamplingData = true;
1497
+ this._transport = transport;
1498
+ const hasCaster = !!(settings?.lightTracing || settings?.photonMapping) &&
1499
+ this._meshes.some((mesh) => mesh.materials.some(isCausticSpecular));
1500
+ const photonSupported = !(settings?.lightTracing || settings?.photonMapping) ||
1501
+ !hasCaster ||
1502
+ !hasStochasticCausticCaster(this._meshes);
1503
+ this._lightTracing = !!settings?.lightTracing && hasCaster && photonSupported;
1504
+ const photonMapping = !!settings?.photonMapping && hasCaster && photonSupported;
1505
+ this._photonMapping = photonMapping;
1506
+ this._causticKey =
1507
+ this._transport?.enabled || this._lightTracing || this._photonMapping
1508
+ ? `${this._lightTracing}/${this._photonMapping}/${causticDiagnostics.fullEmissionRandom !== false}/${causticDiagnostics.photonMaxDepth ?? 20}/${causticDiagnostics.debugGroup}/${causticDiagnostics.photonDirectSubset !== false}/${this._transport?.key ?? ''}`
1509
+ : '';
1270
1510
  }
1271
1511
  _setEnvMap(envImage) {
1272
1512
  const device = this._device;
@@ -1621,14 +1861,19 @@ class WGPURayTracePass {
1621
1861
  this._needsUpdateBindGroups ||= resized;
1622
1862
  }
1623
1863
  _updateLightSamplingData() {
1624
- this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map(light => light.type), this._emissiveTriangles.length);
1625
- const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource ? proceduralEnvironmentPower(this._envImageSource) : estimateEnvironmentPower(this._envImageSource), this._device.limits.maxTextureDimension2D, this._lightChoices, this._lightSamplingScratch, this._shadowCatcherLightChoices);
1864
+ this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map((light) => light.type), this._emissiveTriangles.length);
1865
+ const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource
1866
+ ? proceduralEnvironmentPower(this._envImageSource)
1867
+ : estimateEnvironmentPower(this._envImageSource), this._device.limits.maxTextureDimension2D, this._lightChoices, this._lightSamplingScratch, this._shadowCatcherLightChoices);
1868
+ researchTransport()?.prepare(this, 'webgpu', lighting);
1626
1869
  this._analyticLightSelectionPmfs = lighting.analyticPmfs;
1627
1870
  this._iesProfileTexelOffsets = lighting.profiles.offsets;
1628
1871
  this._lightDomainDiagnostics = lighting.domains;
1629
1872
  const { lightWidth: width, lightHeight: height, lightData: textureData } = lighting;
1630
1873
  writeTraceLighting(this._rtUniform.views, lighting);
1631
- const resized = !this._lightSamplingTexture || width !== this._lightSamplingTextureSize[0] || height !== this._lightSamplingTextureSize[1];
1874
+ const resized = !this._lightSamplingTexture ||
1875
+ width !== this._lightSamplingTextureSize[0] ||
1876
+ height !== this._lightSamplingTextureSize[1];
1632
1877
  if (!this._lightSamplingTexture ||
1633
1878
  width !== this._lightSamplingTextureSize[0] ||
1634
1879
  height !== this._lightSamplingTextureSize[1]) {
@@ -1675,22 +1920,54 @@ class WGPURayTracePass {
1675
1920
  const stacks = getTraversalStackSizes(tlas, blases);
1676
1921
  this._constants.TLAS_STACK_SIZE = retainedShaderCapacity(stacks.tlas, Number(this._constants.TLAS_STACK_SIZE ?? 0));
1677
1922
  this._constants.BLAS_STACK_SIZE = retainedShaderCapacity(stacks.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32);
1678
- const tlasBuffer = new Float32Array(tlas.buffer);
1679
- const blasBuffer = new Float32Array(concatBLASBuffers(blases));
1680
- const bvhData = new Float32Array(tlasBuffer.length + blasBuffer.length);
1681
- const blasOffset = tlasBuffer.length / 4;
1682
- bvhData.set(tlasBuffer);
1683
- bvhData.set(blasBuffer, blasOffset * 4);
1684
- const bvhBuffer = this._updateGPUBuffer('BVH', this._bvhBuffer, bvhData);
1923
+ const size = tlas.buffer.byteLength + blases.reduce((sum, b) => sum + b.bvh.buffer.byteLength, 0);
1924
+ const limit = Math.min(this._device.limits.maxBufferSize, this._device.limits.maxStorageBufferBindingSize);
1925
+ if (size > limit)
1926
+ throw new RangeError(`BVH buffer requires ${size} bytes; device storage buffer limit is ${limit} bytes.`);
1927
+ let bvhBuffer = this._bvhBuffer;
1928
+ if (!bvhBuffer || bvhBuffer.size < Math.max(size, WGPU_SCENE_BUFFER_MIN_BYTES)) {
1929
+ bvhBuffer?.destroy();
1930
+ bvhBuffer = this._device.createBuffer({ label: 'BVH', size: Math.max(size, WGPU_SCENE_BUFFER_MIN_BYTES),
1931
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, mappedAtCreation: size < 0x80000000 });
1932
+ if (size < 0x80000000) {
1933
+ this._writeBVH(bvhBuffer.getMappedRange(), tlas, blases);
1934
+ bvhBuffer.unmap();
1935
+ }
1936
+ else
1937
+ this._uploadBVHBlocks(bvhBuffer, tlas, blases);
1938
+ }
1939
+ else
1940
+ this._uploadBVHBlocks(bvhBuffer, tlas, blases);
1941
+ this._bvhCPU = { tlas, blases };
1942
+ this._bvhDataBytes = size;
1943
+ const blasOffset = tlas.buffer.byteLength / 16;
1685
1944
  const instancesBuffer = this._updateGPUBuffer('Instances', this._instancesBuffer, new Float32Array(tlas.instancesBuffer));
1686
- if (bvhBuffer !== this._bvhBuffer || instancesBuffer !== this._instancesBuffer) {
1945
+ if (bvhBuffer !== this._bvhBuffer || instancesBuffer !== this._instancesBuffer)
1687
1946
  this._needsUpdateBindGroups = true;
1688
- }
1689
1947
  this._bvhBuffer = bvhBuffer;
1690
1948
  this._instancesBuffer = instancesBuffer;
1691
- this._debugBVHBuffer = bvhData.buffer.slice(0);
1692
1949
  this._rtUniform.views.blasOffset.set([blasOffset]);
1693
1950
  }
1951
+ _uploadBVHBlocks(buffer, tlas, blases) {
1952
+ this._device.queue.writeBuffer(buffer, 0, tlas.buffer);
1953
+ let target = tlas.buffer.byteLength;
1954
+ for (const blas of blases) {
1955
+ const chunkBytes = 64 * 1024 * 1024;
1956
+ for (let offset = 0; offset < blas.bvh.buffer.byteLength; offset += chunkBytes) {
1957
+ this._device.queue.writeBuffer(buffer, target + offset, blas.bvh.buffer, offset, Math.min(chunkBytes, blas.bvh.buffer.byteLength - offset));
1958
+ }
1959
+ target += blas.bvh.buffer.byteLength;
1960
+ }
1961
+ }
1962
+ _writeBVH(buffer, tlas, blases) {
1963
+ const bytes = new Uint8Array(buffer);
1964
+ bytes.set(new Uint8Array(tlas.buffer));
1965
+ let offset = tlas.buffer.byteLength;
1966
+ for (const blas of blases) {
1967
+ bytes.set(new Uint8Array(blas.bvh.buffer), offset);
1968
+ offset += blas.bvh.buffer.byteLength;
1969
+ }
1970
+ }
1694
1971
  _updateMeshData() {
1695
1972
  this._pendingGeometryUpdates.clear();
1696
1973
  this._emissiveCache = new EmissiveTriangleCache();
@@ -1705,7 +1982,7 @@ class WGPURayTracePass {
1705
1982
  }
1706
1983
  this._geometriesNeedRefit.clear();
1707
1984
  // TODO check bvh needs to change
1708
- const res = updateMeshInstances(meshes);
1985
+ const res = updateMeshInstances(meshes, { mergeAttributes: false });
1709
1986
  this._preparedMeshData = res;
1710
1987
  this._membershipCache.capture(res);
1711
1988
  this._pendingMembershipData = undefined;
@@ -1713,7 +1990,7 @@ class WGPURayTracePass {
1713
1990
  const mergedGeometry = res.geometry;
1714
1991
  const materials = res.materials;
1715
1992
  const isEmpty = meshes.length === 0 || res.instances.length === 0;
1716
- const tlas = buildTLAS(res.instances, res.blases);
1993
+ const tlas = buildWebTLAS(res.instances, res.blases);
1717
1994
  this._tlas = tlas;
1718
1995
  this._uploadBVH(tlas, res.blases);
1719
1996
  const materialData = (this._materialData = this._createMaterialData(materials, this._analyticLights, {
@@ -1723,13 +2000,23 @@ class WGPURayTracePass {
1723
2000
  }));
1724
2001
  this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, materials);
1725
2002
  this._needsUpdateLightSamplingData = true;
1726
- const attributeLayout = sceneAttributeLayout(mergedGeometry);
2003
+ // Displacement geometry and its CPU BVH bounds must use the same normal.
2004
+ // Preserve f32 normals in these scenes, avoiding quantization outside bounds.
2005
+ const attributeLayout = compactVertexLayout(res.geometries, materials.some(m => !!m.displacementMap));
1727
2006
  const { stride, offsets } = attributeLayout;
1728
- const attrBufferData = new F32(mergedGeometry.vertexCount * stride);
1729
- packVertexAttributes(mergedGeometry, attrBufferData, stride, offsets, 0, mergedGeometry.vertexCount);
2007
+ const vertexWords = res.geometries.reduce((sum, g) => sum + g.vertexCount, 0) * stride;
2008
+ const materialWords = attributeLayout.constants.HAS_TRIANGLE_MATERIAL ? this._bvhDataBytes / 16 : 0;
2009
+ const attrBufferData = new Uint32Array(vertexWords + materialWords);
2010
+ attributeLayout.constants.MATERIALINDEX_OFF = vertexWords;
2011
+ for (const geometry of res.geometries) {
2012
+ const region = res.geometryRegions.get(geometry);
2013
+ packCompactVertices(geometry, attrBufferData, stride, offsets, 0, geometry.vertexCount, region.vertexOffset, false, !!attributeLayout.constants.COLOR_FLOAT, !!attributeLayout.constants.NORMAL_FLOAT);
2014
+ }
1730
2015
  this._attributeData = attrBufferData;
1731
2016
  this._attributeStride = stride;
1732
2017
  this._attributeOffsets = offsets;
2018
+ Object.assign(this._constants, attributeLayout.constants);
2019
+ this._packTriangleMaterials(res, tlas);
1733
2020
  this._attrBuffers = this._updateGPUBuffer('Attributes', this._attrBuffers, attrBufferData);
1734
2021
  this._materialsBuffer = this._updateGPUBuffer('Materials', this._materialsBuffer, materialData.getBufferArray(),
1735
2022
  // WGSL requires a runtime array binding to contain at least one
@@ -1770,21 +2057,53 @@ class WGPURayTracePass {
1770
2057
  }
1771
2058
  this._hasShadowCatcher = materials.some((m) => m.shadowCatcher);
1772
2059
  const hasDisplacement = materialData.getDisplacementMaps().length > 0;
1773
- if (hasDisplacement !== this._hasDisplacement) {
1774
- this._hasDisplacement = hasDisplacement;
1775
- // Needs to recreate pipeline layout
1776
- this._rayTracePipelineLayout = undefined;
1777
- }
2060
+ this._hasDisplacement = hasDisplacement;
1778
2061
  Object.assign(this._constants, wgpuMaterialConstants(materials, materialData, mergedGeometry, this._bevelSamples, Number(this._constants.NUM_MAPS ?? 0)));
1779
2062
  // Runtime storage metadata has no texture-count specialization.
1780
2063
  if (this._bindless)
1781
2064
  this._constants.NUM_MAPS = 20;
1782
2065
  }
2066
+ _packTriangleMaterials(res, tlas) {
2067
+ if (!this._constants.HAS_TRIANGLE_MATERIAL)
2068
+ return;
2069
+ let offset = Number(this._constants.MATERIALINDEX_OFF) + tlas.buffer.byteLength / 16;
2070
+ res.geometries.forEach((g, i) => {
2071
+ packTriangleMaterials(g, res.geometryRegions.get(g).materialIndices, res.blases[i].bvh.buffer, this._attributeData, offset);
2072
+ offset += res.blases[i].bvh.buffer.byteLength / 16;
2073
+ });
2074
+ }
2075
+ _refreshTriangleMaterials(res, tlas) {
2076
+ if (!this._constants.HAS_TRIANGLE_MATERIAL)
2077
+ return;
2078
+ const vertexWords = Number(this._constants.MATERIALINDEX_OFF);
2079
+ const required = vertexWords + this._bvhDataBytes / 16;
2080
+ if (this._attributeData.length !== required) {
2081
+ const next = new Uint32Array(required);
2082
+ next.set(this._attributeData.subarray(0, vertexWords));
2083
+ this._attributeData = next;
2084
+ }
2085
+ this._packTriangleMaterials(res, tlas);
2086
+ if (this._attrBuffers.size < this._attributeData.byteLength) {
2087
+ this._attrBuffers = this._updateGPUBuffer('Attributes', this._attrBuffers, this._attributeData);
2088
+ }
2089
+ else {
2090
+ const first = vertexWords * 4, end = this._attributeData.byteLength, chunk = 64 * 1024 * 1024;
2091
+ for (let offset = first; offset < end; offset += chunk) {
2092
+ this._device.queue.writeBuffer(this._attrBuffers, offset, this._attributeData.buffer, offset, Math.min(chunk, end - offset));
2093
+ }
2094
+ }
2095
+ }
1783
2096
  _updateGeometryData() {
1784
2097
  const res = this._preparedMeshData, tlas = this._tlas;
2098
+ // Promote the scene color plane if an edit introduces HDR/negative RGB.
2099
+ if (!this._constants.COLOR_FLOAT && [...this._pendingGeometryUpdates].some(g => g.color?.array.some(value => !(value >= 0 && value <= 1)))) {
2100
+ this._updateMeshData();
2101
+ return;
2102
+ }
1785
2103
  const update = updatePreparedGeometries(res, this._pendingGeometryUpdates);
1786
- for (const { first, count, topologyChanged, deformationOnly } of update.vertices) {
1787
- packVertexAttributes(res.geometry, this._attributeData, this._attributeStride, this._attributeOffsets, first, count, topologyChanged, deformationOnly);
2104
+ for (const { first, count, deformationOnly } of update.vertices) {
2105
+ const geometry = res.geometries.find(g => res.geometryRegions.get(g).vertexOffset === first);
2106
+ packCompactVertices(geometry, this._attributeData, this._attributeStride, this._attributeOffsets, 0, count, first, deformationOnly, !!this._constants.COLOR_FLOAT, !!this._constants.NORMAL_FLOAT);
1788
2107
  const offset = first * this._attributeStride * 4, size = count * this._attributeStride * 4;
1789
2108
  this._device.queue.writeBuffer(this._attrBuffers, offset, this._attributeData.buffer, offset, size);
1790
2109
  }
@@ -1800,17 +2119,13 @@ class WGPURayTracePass {
1800
2119
  this._uploadBVH(tlas, res.blases);
1801
2120
  }
1802
2121
  else {
1803
- const bytes = new Uint8Array(this._debugBVHBuffer);
1804
- const ranges = [];
1805
2122
  for (const { blas, offset } of update.blases) {
1806
- const target = tlas.buffer.byteLength + offset;
1807
- bytes.set(new Uint8Array(blas.buffer), target);
1808
- ranges.push({ offset: target, size: blas.buffer.byteLength });
1809
- }
1810
- for (const { offset, size } of mergeRefitRanges(ranges)) {
1811
- this._device.queue.writeBuffer(this._bvhBuffer, offset, this._debugBVHBuffer, offset, size);
2123
+ this._device.queue.writeBuffer(this._bvhBuffer, tlas.buffer.byteLength + offset, blas.buffer);
1812
2124
  }
1813
2125
  }
2126
+ if (this._constants.HAS_TRIANGLE_MATERIAL && (update.relocated || update.vertices.some(v => v.topologyChanged))) {
2127
+ this._refreshTriangleMaterials(res, tlas);
2128
+ }
1814
2129
  const bits = { bvh2: 1, bvh4: 2, 'bvh-gpu': 4, cwbvh: 8 };
1815
2130
  this._blasLayoutMask = res.blases.reduce((mask, b) => mask | bits[b.bvh.layout ?? 'bvh2'], 0);
1816
2131
  const stacks = getTraversalStackSizes(tlas, res.blases);
@@ -1870,9 +2185,10 @@ class WGPURayTracePass {
1870
2185
  if (pending)
1871
2186
  this._residentPreparedMeshData = res;
1872
2187
  this._pendingMembershipData = undefined;
1873
- const tlas = buildTLAS(instances, res.blases);
2188
+ const tlas = buildWebTLAS(instances, res.blases);
1874
2189
  this._tlas = tlas;
1875
2190
  this._uploadBVH(tlas, res.blases);
2191
+ this._refreshTriangleMaterials(res, tlas);
1876
2192
  this._emissiveTriangles = this._emissiveCache.collect(emissiveBindings, res.materials);
1877
2193
  this._needsUpdateLightSamplingData = true;
1878
2194
  this._rtUniform.views.isEmpty[0] = +(instances.length === 0);
@@ -1906,8 +2222,9 @@ class WGPURayTracePass {
1906
2222
  const previousInstances = res.instances;
1907
2223
  res.instances = updateMeshInstanceTransforms(res.instanceBindings, incremental ? res.instances : undefined, incremental ? dirtyIndices : undefined);
1908
2224
  if (!sameInstanceMembership(previousInstances, res.instances)) {
1909
- this._tlas = buildTLAS(res.instances, res.blases);
2225
+ this._tlas = buildWebTLAS(res.instances, res.blases);
1910
2226
  this._uploadBVH(this._tlas, res.blases);
2227
+ this._refreshTriangleMaterials(res, this._tlas);
1911
2228
  this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
1912
2229
  this._needsUpdateLightSamplingData = true;
1913
2230
  this._rtUniform.views.isEmpty[0] = +(res.instances.length === 0);
@@ -1916,16 +2233,12 @@ class WGPURayTracePass {
1916
2233
  return;
1917
2234
  }
1918
2235
  const ranges = refitTLAS(tlas, res.instances, res.blases, incremental ? dirtyIndices : undefined);
1919
- const tlasData = new Float32Array(tlas.buffer);
1920
2236
  for (const { offset, size } of ranges.tlasRanges) {
1921
2237
  this._device.queue.writeBuffer(this._bvhBuffer, offset, tlas.buffer, offset, size);
1922
2238
  }
1923
2239
  for (const { offset, size } of ranges.instanceRanges) {
1924
2240
  this._device.queue.writeBuffer(this._instancesBuffer, offset, tlas.instancesBuffer, offset, size);
1925
2241
  }
1926
- if (this._debugBVHBuffer) {
1927
- new Float32Array(this._debugBVHBuffer).set(tlasData);
1928
- }
1929
2242
  if (!incremental ||
1930
2243
  res.emissiveBindings.some(({ mesh }) => this._dirtyTransformMeshes.has(mesh))) {
1931
2244
  this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
@@ -1945,8 +2258,6 @@ class WGPURayTracePass {
1945
2258
  this._retiredBindless.add(retired);
1946
2259
  void this._device.queue.onSubmittedWorkDone().catch(() => { }).then(() => { retired.dispose(); this._retiredBindless.delete(retired); });
1947
2260
  this._bindless = undefined;
1948
- this._computeShaderModule = undefined;
1949
- this._rayTracePipelineLayout = undefined;
1950
2261
  }
1951
2262
  return new MaterialTable(...args);
1952
2263
  }
@@ -2058,10 +2369,15 @@ class WGPURayTracePass {
2058
2369
  }
2059
2370
  // TODO reuse the code of RayTraceNode
2060
2371
  setCamera(camera) {
2372
+ this._causticPerspective = camera.projection === 'perspective';
2373
+ const projection = camera.getProjectionMatrix(this._targetAspect);
2374
+ this._causticCameraScale = Math.abs(projection[0] * projection[5]) / 4;
2375
+ this._setCaustics(this._causticSettings);
2061
2376
  const viewProjectionInvMat = this._viewProjInv;
2062
2377
  this._apertureRadius = cameraApertureRadius(camera);
2063
2378
  this._focusDistance = camera.focusDistance;
2064
- this._updateViewProjInv(camera, viewProjectionInvMat);
2379
+ mat4.invert(this._projectionInv, projection);
2380
+ mat4.mul(viewProjectionInvMat, camera.transform, this._projectionInv);
2065
2381
  mat4.copy(this._cameraWorld, camera.transform);
2066
2382
  }
2067
2383
  cameraChanged(camera) {
@@ -2117,7 +2433,7 @@ class WGPURayTracePass {
2117
2433
  const arr = this._rtUniform.views.jitter;
2118
2434
  arr[0] = x;
2119
2435
  arr[1] = y;
2120
- this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
2436
+ this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance, this._projectionInv));
2121
2437
  }
2122
2438
  setSampleTiles(columns, rows, tileX, tileY) {
2123
2439
  const arr = this._rtUniform.views.scaleOffset;
@@ -2147,12 +2463,14 @@ class WGPURayTracePass {
2147
2463
  this._envDistribution?.dispose();
2148
2464
  this._skyBaker?.dispose();
2149
2465
  this._bindless?.dispose();
2150
- this._retiredBindless.forEach(textures => textures.dispose());
2466
+ this._retiredBindless.forEach((textures) => textures.dispose());
2151
2467
  this._retiredBindless.clear();
2468
+ this._causticLight?.dispose();
2469
+ this._photonMap?.dispose();
2152
2470
  this._shaderCache.clear();
2153
2471
  this._shaderCapabilities.clear();
2154
2472
  const buffers = new Set([
2155
- ...Array.from(this._targetOutputs.values()).flatMap(target => target.buffers),
2473
+ ...Array.from(this._targetOutputs.values()).flatMap((target) => target.buffers),
2156
2474
  this._mapInfoUniform,
2157
2475
  this._attrBuffers,
2158
2476
  this._materialsBuffer,
@@ -2172,6 +2490,9 @@ class WGPURayTracePass {
2172
2490
  this._adaptiveSamplingTexture
2173
2491
  ]);
2174
2492
  textures.forEach((texture) => texture?.destroy());
2493
+ for (const job of this._queuedCompiles.splice(0))
2494
+ job.release();
2495
+ this._traceVariants.clear();
2175
2496
  this._rayTracePipeline = undefined;
2176
2497
  this._rayTraceBindGroups = undefined;
2177
2498
  }