@meimu/mutracer 0.1.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (590) hide show
  1. package/LICENSES/Classroom-CC0.txt +121 -0
  2. package/README.md +10 -7
  3. package/THIRD_PARTY_NOTICES.md +13 -8
  4. package/dist/assets/bvh.worker.js +1 -1
  5. package/dist/mutracer.umd.js +12316 -9228
  6. package/docs/api.md +232 -21
  7. package/docs/caustics.md +80 -0
  8. package/docs/denoising.md +30 -15
  9. package/docs/materials.md +6 -5
  10. package/docs/performance.md +5 -2
  11. package/docs/progressive-rendering.md +73 -1
  12. package/docs/technical/continuous-spectrum.md +93 -0
  13. package/docs/threejs.md +14 -5
  14. package/docs/updates.md +46 -2
  15. package/docs/viewer-checklist.md +65 -0
  16. package/docs/viewer.md +15 -8
  17. package/lib/Display.d.ts +13 -2
  18. package/lib/Display.js +28 -7
  19. package/lib/Display.js.map +1 -1
  20. package/lib/Engine.d.ts +4 -0
  21. package/lib/Engine.js +99 -37
  22. package/lib/Engine.js.map +1 -1
  23. package/lib/Render.d.ts +98 -15
  24. package/lib/Render.js +275 -27
  25. package/lib/Render.js.map +1 -1
  26. package/lib/RenderLoop.d.ts +11 -1
  27. package/lib/RenderLoop.js +120 -15
  28. package/lib/RenderLoop.js.map +1 -1
  29. package/lib/main.d.ts +6 -3
  30. package/lib/main.js +1 -0
  31. package/lib/main.js.map +1 -1
  32. package/lib/math/access.d.ts +4 -0
  33. package/lib/math/access.js +10 -0
  34. package/lib/math/access.js.map +1 -0
  35. package/lib/math/mat3.d.ts +4 -0
  36. package/lib/math/mat3.js +54 -0
  37. package/lib/math/mat3.js.map +1 -0
  38. package/lib/math/mat4.d.ts +19 -0
  39. package/lib/math/mat4.js +413 -0
  40. package/lib/math/mat4.js.map +1 -0
  41. package/lib/math/storage.d.ts +6 -0
  42. package/lib/math/storage.js +22 -0
  43. package/lib/math/storage.js.map +1 -0
  44. package/lib/math/types.d.ts +3 -0
  45. package/lib/math/types.js +2 -0
  46. package/lib/math/types.js.map +1 -0
  47. package/lib/math/vec2.d.ts +13 -0
  48. package/lib/math/vec2.js +52 -0
  49. package/lib/math/vec2.js.map +1 -0
  50. package/lib/math/vec3.d.ts +23 -0
  51. package/lib/math/vec3.js +139 -0
  52. package/lib/math/vec3.js.map +1 -0
  53. package/lib/math.d.ts +5 -17
  54. package/lib/math.js +5 -55
  55. package/lib/math.js.map +1 -1
  56. package/lib/oidn.d.ts +1 -1
  57. package/lib/oidn.js +4 -2
  58. package/lib/oidn.js.map +1 -1
  59. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  60. package/lib/renderer/AovPrimitiveCache.d.ts +10 -0
  61. package/lib/renderer/AovPrimitiveCache.js +48 -0
  62. package/lib/renderer/AovPrimitiveCache.js.map +1 -0
  63. package/lib/renderer/Box3.d.ts +5 -0
  64. package/lib/renderer/Box3.js +9 -0
  65. package/lib/renderer/Box3.js.map +1 -0
  66. package/lib/renderer/GPUTraceQueue.d.ts +11 -0
  67. package/lib/renderer/GPUTraceQueue.js +33 -0
  68. package/lib/renderer/GPUTraceQueue.js.map +1 -0
  69. package/lib/renderer/MaterialData.d.ts +15 -0
  70. package/lib/renderer/MaterialData.js +61 -0
  71. package/lib/renderer/MaterialData.js.map +1 -0
  72. package/lib/renderer/MaterialTable.d.ts +11 -98
  73. package/lib/renderer/MaterialTable.js +34 -412
  74. package/lib/renderer/MaterialTable.js.map +1 -1
  75. package/lib/renderer/RenderingPipeline.d.ts +36 -13
  76. package/lib/renderer/RenderingPipeline.js +1 -17
  77. package/lib/renderer/RenderingPipeline.js.map +1 -1
  78. package/lib/renderer/TileRender.d.ts +0 -1
  79. package/lib/renderer/TileRender.js +14 -1
  80. package/lib/renderer/TileRender.js.map +1 -1
  81. package/lib/renderer/WGPUAov.d.ts +39 -0
  82. package/lib/renderer/WGPUAov.js +239 -0
  83. package/lib/renderer/WGPUAov.js.map +1 -0
  84. package/lib/renderer/WGPUBindlessMaterialData.d.ts +17 -0
  85. package/lib/renderer/WGPUBindlessMaterialData.js +48 -0
  86. package/lib/renderer/WGPUBindlessMaterialData.js.map +1 -0
  87. package/lib/renderer/WGPUBindlessTextures.d.ts +47 -0
  88. package/lib/renderer/WGPUBindlessTextures.js +180 -0
  89. package/lib/renderer/WGPUBindlessTextures.js.map +1 -0
  90. package/lib/renderer/WGPUBloomPass.js +17 -97
  91. package/lib/renderer/WGPUBloomPass.js.map +1 -1
  92. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  93. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  94. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  95. package/lib/renderer/WGPUComputePass.d.ts +1 -1
  96. package/lib/renderer/WGPUComputePass.js.map +1 -1
  97. package/lib/renderer/WGPUEnvMapDistribution.d.ts +12 -0
  98. package/lib/renderer/WGPUEnvMapDistribution.js +150 -0
  99. package/lib/renderer/WGPUEnvMapDistribution.js.map +1 -0
  100. package/lib/renderer/WGPUFullQuadPass.js +10 -9
  101. package/lib/renderer/WGPUFullQuadPass.js.map +1 -1
  102. package/lib/renderer/WGPUJNLMDenoiser.d.ts +1 -20
  103. package/lib/renderer/WGPUJNLMDenoiser.js +4 -267
  104. package/lib/renderer/WGPUJNLMDenoiser.js.map +1 -1
  105. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  106. package/lib/renderer/WGPUPhotonMap.js +191 -0
  107. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  108. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  109. package/lib/renderer/WGPURayTraceOutput.js +132 -29
  110. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  111. package/lib/renderer/WGPURayTracePass.d.ts +83 -56
  112. package/lib/renderer/WGPURayTracePass.js +884 -837
  113. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  114. package/lib/renderer/WGPURenderingPipeline.d.ts +5 -3
  115. package/lib/renderer/WGPURenderingPipeline.js +297 -82
  116. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  117. package/lib/renderer/WGPUShaderCache.d.ts +2 -2
  118. package/lib/renderer/WGPUShaderCache.js +10 -5
  119. package/lib/renderer/WGPUShaderCache.js.map +1 -1
  120. package/lib/renderer/WGPUShaderCapabilities.d.ts +10 -0
  121. package/lib/renderer/WGPUShaderCapabilities.js +54 -0
  122. package/lib/renderer/WGPUShaderCapabilities.js.map +1 -0
  123. package/lib/renderer/WGPUSkyBaker.d.ts +13 -0
  124. package/lib/renderer/WGPUSkyBaker.js +85 -0
  125. package/lib/renderer/WGPUSkyBaker.js.map +1 -0
  126. package/lib/renderer/WebGLAov.d.ts +41 -0
  127. package/lib/renderer/WebGLAov.js +106 -0
  128. package/lib/renderer/WebGLAov.js.map +1 -0
  129. package/lib/renderer/WebGLFullQuadPass.d.ts +7 -4
  130. package/lib/renderer/WebGLFullQuadPass.js +22 -2
  131. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  132. package/lib/renderer/WebGLJNLMDenoiser.d.ts +1 -1
  133. package/lib/renderer/WebGLJNLMDenoiser.js +83 -74
  134. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
  135. package/lib/renderer/WebGLRayTraceOutputPass.js +7 -4
  136. package/lib/renderer/WebGLRayTraceOutputPass.js.map +1 -1
  137. package/lib/renderer/WebGLRayTracePass.d.ts +18 -3
  138. package/lib/renderer/WebGLRayTracePass.js +121 -9
  139. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  140. package/lib/renderer/WebGLRenderingPipeline.js +126 -22
  141. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  142. package/lib/renderer/WebGLSkyBaker.d.ts +39 -0
  143. package/lib/renderer/WebGLSkyBaker.js +422 -0
  144. package/lib/renderer/WebGLSkyBaker.js.map +1 -0
  145. package/lib/renderer/WebGLSkySource.d.ts +9 -0
  146. package/lib/renderer/WebGLSkySource.js +28 -0
  147. package/lib/renderer/WebGLSkySource.js.map +1 -0
  148. package/lib/renderer/accumulationTarget.d.ts +6 -0
  149. package/lib/renderer/accumulationTarget.js.map +1 -1
  150. package/lib/renderer/aov.d.ts +42 -0
  151. package/lib/renderer/aov.js +50 -0
  152. package/lib/renderer/aov.js.map +1 -0
  153. package/lib/renderer/aovDisplay.d.ts +14 -0
  154. package/lib/renderer/aovDisplay.js +32 -0
  155. package/lib/renderer/aovDisplay.js.map +1 -0
  156. package/lib/renderer/aovScene.d.ts +21 -0
  157. package/lib/renderer/aovScene.js +139 -0
  158. package/lib/renderer/aovScene.js.map +1 -0
  159. package/lib/renderer/binnedSahBvh.js +2 -1
  160. package/lib/renderer/binnedSahBvh.js.map +1 -1
  161. package/lib/renderer/bvhAccel.d.ts +20 -10
  162. package/lib/renderer/bvhAccel.js +168 -58
  163. package/lib/renderer/bvhAccel.js.map +1 -1
  164. package/lib/renderer/bvhWorkerBuild.d.ts +1 -0
  165. package/lib/renderer/cameraSnapshot.js +2 -1
  166. package/lib/renderer/cameraSnapshot.js.map +1 -1
  167. package/lib/renderer/cameraUniform.d.ts +2 -0
  168. package/lib/renderer/cameraUniform.js +12 -0
  169. package/lib/renderer/cameraUniform.js.map +1 -0
  170. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  171. package/lib/renderer/causticDiagnostics.js +5 -0
  172. package/lib/renderer/causticDiagnostics.js.map +1 -0
  173. package/lib/renderer/causticEmission.d.ts +10 -0
  174. package/lib/renderer/causticEmission.js +40 -0
  175. package/lib/renderer/causticEmission.js.map +1 -0
  176. package/lib/renderer/causticUniform.d.ts +12 -0
  177. package/lib/renderer/causticUniform.js +22 -0
  178. package/lib/renderer/causticUniform.js.map +1 -0
  179. package/lib/renderer/caustics.d.ts +16 -0
  180. package/lib/renderer/caustics.js +93 -0
  181. package/lib/renderer/caustics.js.map +1 -0
  182. package/lib/renderer/committedScene.js +4 -0
  183. package/lib/renderer/committedScene.js.map +1 -1
  184. package/lib/renderer/denoiser.d.ts +3 -11
  185. package/lib/renderer/denoiser.js.map +1 -1
  186. package/lib/renderer/depthQuery.js +4 -1
  187. package/lib/renderer/depthQuery.js.map +1 -1
  188. package/lib/renderer/displacementPreparation.d.ts +11 -0
  189. package/lib/renderer/displacementPreparation.js +351 -0
  190. package/lib/renderer/displacementPreparation.js.map +1 -0
  191. package/lib/renderer/displacementRange.d.ts +1 -0
  192. package/lib/renderer/displacementRange.js +9 -0
  193. package/lib/renderer/displacementRange.js.map +1 -0
  194. package/lib/renderer/displayParityDiagnostics.d.ts +11 -0
  195. package/lib/renderer/displayParityDiagnostics.js +2 -0
  196. package/lib/renderer/displayParityDiagnostics.js.map +1 -0
  197. package/lib/renderer/envMapCreation.d.ts +9 -8
  198. package/lib/renderer/envMapCreation.js +36 -12
  199. package/lib/renderer/envMapCreation.js.map +1 -1
  200. package/lib/renderer/envMapDistribution.d.ts +12 -2
  201. package/lib/renderer/envMapDistribution.js +43 -34
  202. package/lib/renderer/envMapDistribution.js.map +1 -1
  203. package/lib/renderer/geometryBvhBounds.d.ts +4 -0
  204. package/lib/renderer/geometryBvhBounds.js +82 -0
  205. package/lib/renderer/geometryBvhBounds.js.map +1 -0
  206. package/lib/renderer/geometryUpdateState.d.ts +32 -0
  207. package/lib/renderer/geometryUpdateState.js +2 -0
  208. package/lib/renderer/geometryUpdateState.js.map +1 -0
  209. package/lib/renderer/geometryUpdates.d.ts +17 -9
  210. package/lib/renderer/geometryUpdates.js +92 -33
  211. package/lib/renderer/geometryUpdates.js.map +1 -1
  212. package/lib/renderer/glsl/aovDisplay.frag.d.ts +3 -0
  213. package/lib/renderer/glsl/aovDisplay.frag.js +54 -0
  214. package/lib/renderer/glsl/aovDisplay.frag.js.map +1 -0
  215. package/lib/renderer/glsl/aovReflectance.glsl.d.ts +2 -0
  216. package/lib/renderer/glsl/aovReflectance.glsl.js +87 -0
  217. package/lib/renderer/glsl/aovReflectance.glsl.js.map +1 -0
  218. package/lib/renderer/glsl/aovReflectanceSamples.glsl.d.ts +2 -0
  219. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +135 -0
  220. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js.map +1 -0
  221. package/lib/renderer/glsl/aovTrace.frag.d.ts +2 -0
  222. package/lib/renderer/glsl/aovTrace.frag.js +121 -0
  223. package/lib/renderer/glsl/aovTrace.frag.js.map +1 -0
  224. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +12 -12
  225. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  226. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  227. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  228. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -67
  229. package/lib/renderer/glsl/chunks/envMap.glsl.js.map +1 -1
  230. package/lib/renderer/glsl/chunks/intersect.glsl.js +432 -434
  231. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  232. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  233. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +160 -157
  234. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  235. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +802 -802
  236. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  237. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  238. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  239. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +72 -67
  240. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  241. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
  242. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  243. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  244. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +143 -120
  245. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  246. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +21 -18
  247. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js.map +1 -1
  248. package/lib/renderer/glsl/chunks/sampling.glsl.js +91 -94
  249. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  250. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  251. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  252. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +12 -12
  253. package/lib/renderer/glsl/chunks/uvProjection.glsl.js.map +1 -1
  254. package/lib/renderer/glsl/depthQuery.frag.js +7 -7
  255. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +17 -9
  256. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js.map +1 -1
  257. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  258. package/lib/renderer/glsl/rayTrace.frag.js +271 -248
  259. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  260. package/lib/renderer/glsl/shadowCatcher.frag.js +37 -32
  261. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  262. package/lib/renderer/glsl/webglPipeline.frag.js +65 -60
  263. package/lib/renderer/glsl/webglPipeline.frag.js.map +1 -1
  264. package/lib/renderer/lightConstants.d.ts +10 -0
  265. package/lib/renderer/lightConstants.js +11 -0
  266. package/lib/renderer/lightConstants.js.map +1 -0
  267. package/lib/renderer/lightCore.d.ts +12 -0
  268. package/lib/renderer/lightCore.js +13 -0
  269. package/lib/renderer/lightCore.js.map +1 -0
  270. package/lib/renderer/lightSampling.d.ts +22 -18
  271. package/lib/renderer/lightSampling.js +135 -62
  272. package/lib/renderer/lightSampling.js.map +1 -1
  273. package/lib/renderer/lightsData.d.ts +21 -0
  274. package/lib/renderer/lightsData.js +192 -0
  275. package/lib/renderer/lightsData.js.map +1 -0
  276. package/lib/renderer/lightsUtil.d.ts +3 -26
  277. package/lib/renderer/lightsUtil.js +3 -153
  278. package/lib/renderer/lightsUtil.js.map +1 -1
  279. package/lib/renderer/materialBufferData.d.ts +70 -0
  280. package/lib/renderer/materialBufferData.js +2 -0
  281. package/lib/renderer/materialBufferData.js.map +1 -0
  282. package/lib/renderer/materialConstants.d.ts +11 -0
  283. package/lib/renderer/materialConstants.js +11 -0
  284. package/lib/renderer/materialConstants.js.map +1 -1
  285. package/lib/renderer/materialCore.d.ts +8 -0
  286. package/lib/renderer/materialCore.js +9 -0
  287. package/lib/renderer/materialCore.js.map +1 -0
  288. package/lib/renderer/materialEmission.d.ts +2 -2
  289. package/lib/renderer/materialEmission.js +3 -3
  290. package/lib/renderer/materialEmission.js.map +1 -1
  291. package/lib/renderer/materialLayout.d.ts +2 -101
  292. package/lib/renderer/materialLayout.js +110 -172
  293. package/lib/renderer/materialLayout.js.map +1 -1
  294. package/lib/renderer/materialLayoutSchema.d.ts +26 -0
  295. package/lib/renderer/materialLayoutSchema.js +172 -0
  296. package/lib/renderer/materialLayoutSchema.js.map +1 -0
  297. package/lib/renderer/materialParams.d.ts +69 -0
  298. package/lib/renderer/materialParams.js +272 -0
  299. package/lib/renderer/materialParams.js.map +1 -0
  300. package/lib/renderer/materialTextureSlots.d.ts +14 -324
  301. package/lib/renderer/materialTextureSlots.js +21 -8
  302. package/lib/renderer/materialTextureSlots.js.map +1 -1
  303. package/lib/renderer/materialTextures.d.ts +6 -0
  304. package/lib/renderer/{texturesFromMaterials.js → materialTextures.js} +1 -2
  305. package/lib/renderer/materialTextures.js.map +1 -0
  306. package/lib/renderer/mergeMeshesToGeometry.js +6 -147
  307. package/lib/renderer/mergeMeshesToGeometry.js.map +1 -1
  308. package/lib/renderer/meshInstances.d.ts +4 -21
  309. package/lib/renderer/meshInstances.js +10 -56
  310. package/lib/renderer/meshInstances.js.map +1 -1
  311. package/lib/renderer/oidnNetwork.d.ts +2 -2
  312. package/lib/renderer/oidnNetwork.js +1 -1
  313. package/lib/renderer/oidnNetwork.js.map +1 -1
  314. package/lib/renderer/openPbrEnergyLut.js +850 -846
  315. package/lib/renderer/openPbrEnergyLut.js.map +1 -1
  316. package/lib/renderer/ownedDenoiser.d.ts +9 -0
  317. package/lib/renderer/ownedDenoiser.js +2 -0
  318. package/lib/renderer/ownedDenoiser.js.map +1 -0
  319. package/lib/renderer/packedMeshInstances.d.ts +23 -0
  320. package/lib/renderer/packedMeshInstances.js +80 -0
  321. package/lib/renderer/packedMeshInstances.js.map +1 -0
  322. package/lib/renderer/prepareMeshBVH.d.ts +12 -47
  323. package/lib/renderer/prepareMeshBVH.js +21 -642
  324. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  325. package/lib/renderer/proceduralEnvironmentPower.d.ts +3 -0
  326. package/lib/renderer/proceduralEnvironmentPower.js +8 -0
  327. package/lib/renderer/proceduralEnvironmentPower.js.map +1 -0
  328. package/lib/renderer/progressiveScheduler.d.ts +1 -0
  329. package/lib/renderer/progressiveScheduler.js +7 -3
  330. package/lib/renderer/progressiveScheduler.js.map +1 -1
  331. package/lib/renderer/renderPipeline.d.ts +4 -1
  332. package/lib/renderer/renderPipeline.js.map +1 -1
  333. package/lib/renderer/renderingLimits.d.ts +9 -0
  334. package/lib/renderer/renderingLimits.js +18 -0
  335. package/lib/renderer/renderingLimits.js.map +1 -0
  336. package/lib/renderer/researchTransport.d.ts +31 -0
  337. package/lib/renderer/researchTransport.js +5 -0
  338. package/lib/renderer/researchTransport.js.map +1 -0
  339. package/lib/renderer/sceneAssembly.d.ts +73 -0
  340. package/lib/renderer/sceneAssembly.js +109 -0
  341. package/lib/renderer/sceneAssembly.js.map +1 -0
  342. package/lib/renderer/sceneChangeCore.d.ts +39 -0
  343. package/lib/renderer/sceneChangeCore.js +307 -0
  344. package/lib/renderer/sceneChangeCore.js.map +1 -0
  345. package/lib/renderer/sceneChangeDetector.d.ts +3 -6
  346. package/lib/renderer/sceneChangeDetector.js +24 -150
  347. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  348. package/lib/renderer/sceneLayoutSchema.d.ts +119 -0
  349. package/lib/renderer/sceneLayoutSchema.js +111 -0
  350. package/lib/renderer/sceneLayoutSchema.js.map +1 -0
  351. package/lib/renderer/sceneLightChanges.d.ts +3 -3
  352. package/lib/renderer/sceneLightChanges.js.map +1 -1
  353. package/lib/renderer/scenePreparation.d.ts +55 -0
  354. package/lib/renderer/scenePreparation.js +360 -0
  355. package/lib/renderer/scenePreparation.js.map +1 -0
  356. package/lib/renderer/scratchSampling.js +18 -18
  357. package/lib/renderer/scratchSampling.js.map +1 -1
  358. package/lib/renderer/shader.d.ts +2 -0
  359. package/lib/renderer/shader.js +2 -0
  360. package/lib/renderer/shader.js.map +1 -1
  361. package/lib/renderer/shaderCapabilities.d.ts +23 -0
  362. package/lib/renderer/shaderCapabilities.js +55 -0
  363. package/lib/renderer/shaderCapabilities.js.map +1 -0
  364. package/lib/renderer/shaderCapacity.d.ts +2 -0
  365. package/lib/renderer/shaderCapacity.js +5 -0
  366. package/lib/renderer/shaderCapacity.js.map +1 -0
  367. package/lib/renderer/skyModel.d.ts +9 -0
  368. package/lib/renderer/skyModel.js +10 -0
  369. package/lib/renderer/skyModel.js.map +1 -0
  370. package/lib/renderer/spectrum.d.ts +21 -0
  371. package/lib/renderer/spectrum.js +154 -0
  372. package/lib/renderer/spectrum.js.map +1 -0
  373. package/lib/renderer/textureAtlas.d.ts +60 -0
  374. package/lib/renderer/textureAtlas.js +145 -0
  375. package/lib/renderer/textureAtlas.js.map +1 -0
  376. package/lib/renderer/textureAtlasPixels.d.ts +7 -0
  377. package/lib/renderer/textureAtlasPixels.js +68 -0
  378. package/lib/renderer/textureAtlasPixels.js.map +1 -0
  379. package/lib/renderer/util.d.ts +1 -1
  380. package/lib/renderer/util.js.map +1 -1
  381. package/lib/renderer/webgl.d.ts +9 -2
  382. package/lib/renderer/webgl.js +54 -9
  383. package/lib/renderer/webgl.js.map +1 -1
  384. package/lib/renderer/webglLightSampling.d.ts +39 -0
  385. package/lib/renderer/webglLightSampling.js +15 -0
  386. package/lib/renderer/webglLightSampling.js.map +1 -0
  387. package/lib/renderer/wgpuBindless.d.ts +26 -0
  388. package/lib/renderer/wgpuBindless.js +95 -0
  389. package/lib/renderer/wgpuBindless.js.map +1 -0
  390. package/lib/renderer/wgpuBindlessSource.d.ts +2 -0
  391. package/lib/renderer/wgpuBindlessSource.js +51 -0
  392. package/lib/renderer/wgpuBindlessSource.js.map +1 -0
  393. package/lib/renderer/wgpuMaterialConstants.d.ts +4 -0
  394. package/lib/renderer/wgpuMaterialConstants.js +52 -0
  395. package/lib/renderer/wgpuMaterialConstants.js.map +1 -0
  396. package/lib/renderer/wgpuSceneUniform.d.ts +42 -0
  397. package/lib/renderer/wgpuSceneUniform.js +125 -0
  398. package/lib/renderer/wgpuSceneUniform.js.map +1 -0
  399. package/lib/renderer/wgpuShaderSource.d.ts +22 -0
  400. package/lib/renderer/wgpuShaderSource.js +33 -0
  401. package/lib/renderer/wgpuShaderSource.js.map +1 -0
  402. package/lib/renderer/wgsl/aovDisplay.wgsl.d.ts +1 -0
  403. package/lib/renderer/wgsl/aovDisplay.wgsl.js +70 -0
  404. package/lib/renderer/wgsl/aovDisplay.wgsl.js.map +1 -0
  405. package/lib/renderer/wgsl/aovReflectance.wgsl.d.ts +2 -0
  406. package/lib/renderer/wgsl/aovReflectance.wgsl.js +98 -0
  407. package/lib/renderer/wgsl/aovReflectance.wgsl.js.map +1 -0
  408. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.d.ts +2 -0
  409. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +135 -0
  410. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js.map +1 -0
  411. package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +5 -0
  412. package/lib/renderer/wgsl/aovResolve.wgsl.js +38 -0
  413. package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -0
  414. package/lib/renderer/wgsl/aovSnapshot.wgsl.d.ts +4 -0
  415. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +21 -0
  416. package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -0
  417. package/lib/renderer/wgsl/aovTrace.wgsl.d.ts +2 -0
  418. package/lib/renderer/wgsl/aovTrace.wgsl.js +115 -0
  419. package/lib/renderer/wgsl/aovTrace.wgsl.js.map +1 -0
  420. package/lib/renderer/wgsl/bloom.wgsl.d.ts +13 -0
  421. package/lib/renderer/wgsl/bloom.wgsl.js +92 -0
  422. package/lib/renderer/wgsl/bloom.wgsl.js.map +1 -0
  423. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  424. package/lib/renderer/wgsl/causticLight.compute.js +537 -0
  425. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  426. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  427. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  428. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  429. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  430. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  431. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  432. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  433. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  434. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  435. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  436. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  437. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  438. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  439. package/lib/renderer/wgsl/chunks/envMap.wgsl.d.ts +1 -1
  440. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +41 -35
  441. package/lib/renderer/wgsl/chunks/envMap.wgsl.js.map +1 -1
  442. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +13 -1
  443. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +747 -704
  444. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  445. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +16 -16
  446. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
  447. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +4 -1
  448. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +83 -79
  449. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  450. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +855 -857
  451. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js.map +1 -1
  452. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +137 -137
  453. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  454. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +3 -1
  455. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +52 -32
  456. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  457. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  458. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +152 -144
  459. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  460. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  461. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +269 -245
  462. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  463. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  464. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +27 -14
  465. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  466. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  467. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +91 -95
  468. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  469. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.d.ts +1 -1
  470. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js +1 -0
  471. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js.map +1 -1
  472. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  473. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +12 -12
  474. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js.map +1 -1
  475. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +8 -3
  476. package/lib/renderer/wgsl/compose.frag.wgsl.js +143 -118
  477. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  478. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  479. package/lib/renderer/wgsl/depthQuery.wgsl.js +7 -7
  480. package/lib/renderer/wgsl/jnlm.compute.d.ts +25 -0
  481. package/lib/renderer/wgsl/jnlm.compute.js +315 -0
  482. package/lib/renderer/wgsl/jnlm.compute.js.map +1 -0
  483. package/lib/renderer/wgsl/offlineAdaptive.compute.js +28 -28
  484. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.d.ts +1 -0
  485. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +17 -0
  486. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js.map +1 -0
  487. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  488. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  489. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  490. package/lib/renderer/wgsl/proceduralSky.compute.d.ts +1 -0
  491. package/lib/renderer/wgsl/proceduralSky.compute.js +198 -0
  492. package/lib/renderer/wgsl/proceduralSky.compute.js.map +1 -0
  493. package/lib/renderer/wgsl/rayTrace.compute.d.ts +25 -1
  494. package/lib/renderer/wgsl/rayTrace.compute.js +645 -500
  495. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  496. package/lib/scene/Camera.js +7 -3
  497. package/lib/scene/Camera.js.map +1 -1
  498. package/lib/scene/EnvironmentLight.d.ts +5 -9
  499. package/lib/scene/EnvironmentLight.js +4 -14
  500. package/lib/scene/EnvironmentLight.js.map +1 -1
  501. package/lib/scene/EnvironmentLightState.d.ts +18 -0
  502. package/lib/scene/EnvironmentLightState.js +19 -0
  503. package/lib/scene/EnvironmentLightState.js.map +1 -0
  504. package/lib/scene/Geometry.d.ts +12 -6
  505. package/lib/scene/Geometry.js +37 -13
  506. package/lib/scene/Geometry.js.map +1 -1
  507. package/lib/scene/InstanceState.d.ts +39 -0
  508. package/lib/scene/InstanceState.js +77 -0
  509. package/lib/scene/InstanceState.js.map +1 -0
  510. package/lib/scene/InstancedMesh.d.ts +1 -1
  511. package/lib/scene/InstancedMesh.js +3 -0
  512. package/lib/scene/InstancedMesh.js.map +1 -1
  513. package/lib/scene/MaterialState.d.ts +209 -0
  514. package/lib/scene/MaterialState.js +231 -0
  515. package/lib/scene/MaterialState.js.map +1 -0
  516. package/lib/scene/Mesh.d.ts +4 -6
  517. package/lib/scene/Mesh.js +3 -11
  518. package/lib/scene/Mesh.js.map +1 -1
  519. package/lib/scene/MeshState.d.ts +20 -0
  520. package/lib/scene/MeshState.js +35 -0
  521. package/lib/scene/MeshState.js.map +1 -0
  522. package/lib/scene/OpenPBRMaterial.d.ts +6 -168
  523. package/lib/scene/OpenPBRMaterial.js +5 -223
  524. package/lib/scene/OpenPBRMaterial.js.map +1 -1
  525. package/lib/scene/PixelTexture.d.ts +6 -0
  526. package/lib/scene/PixelTexture.js +10 -0
  527. package/lib/scene/PixelTexture.js.map +1 -0
  528. package/lib/scene/ProceduralSky.d.ts +24 -0
  529. package/lib/scene/ProceduralSky.js +40 -0
  530. package/lib/scene/ProceduralSky.js.map +1 -0
  531. package/lib/scene/RectLight.d.ts +2 -13
  532. package/lib/scene/RectLight.js +3 -21
  533. package/lib/scene/RectLight.js.map +1 -1
  534. package/lib/scene/RectLightState.d.ts +31 -0
  535. package/lib/scene/RectLightState.js +26 -0
  536. package/lib/scene/RectLightState.js.map +1 -0
  537. package/lib/scene/Scene.d.ts +15 -4
  538. package/lib/scene/Scene.js.map +1 -1
  539. package/lib/scene/SceneNode.js +7 -6
  540. package/lib/scene/SceneNode.js.map +1 -1
  541. package/lib/scene/Texture.d.ts +10 -41
  542. package/lib/scene/Texture.js +4 -33
  543. package/lib/scene/Texture.js.map +1 -1
  544. package/lib/scene/TextureState.d.ts +35 -0
  545. package/lib/scene/TextureState.js +35 -0
  546. package/lib/scene/TextureState.js.map +1 -0
  547. package/lib/scene/attributeData.d.ts +4 -0
  548. package/lib/scene/attributeData.js +10 -0
  549. package/lib/scene/attributeData.js.map +1 -0
  550. package/lib/scene/lightState.d.ts +8 -0
  551. package/lib/scene/lightState.js +2 -0
  552. package/lib/scene/lightState.js.map +1 -0
  553. package/lib/scene/openPbrSchema.d.ts +7 -12
  554. package/lib/scene/openPbrSchema.js +273 -28
  555. package/lib/scene/openPbrSchema.js.map +1 -1
  556. package/lib/scene/skyParameters.d.ts +20 -0
  557. package/lib/scene/skyParameters.js +28 -0
  558. package/lib/scene/skyParameters.js.map +1 -0
  559. package/lib/scene/textureData.d.ts +9 -0
  560. package/lib/scene/textureData.js +2 -0
  561. package/lib/scene/textureData.js.map +1 -0
  562. package/lib/scene/texturePixels.d.ts +6 -0
  563. package/lib/scene/texturePixels.js +10 -0
  564. package/lib/scene/texturePixels.js.map +1 -0
  565. package/lib/settings/display.d.ts +33 -0
  566. package/lib/settings/display.js +7 -0
  567. package/lib/settings/display.js.map +1 -1
  568. package/lib/settings/render.d.ts +36 -3
  569. package/lib/settings/render.js +4 -1
  570. package/lib/settings/render.js.map +1 -1
  571. package/lib/tinybvh/tinybvh.wasm +0 -0
  572. package/lib/util/commonGeometry.js +7 -7
  573. package/lib/util/commonGeometry.js.map +1 -1
  574. package/lib/util/fromTHREE.d.ts +4 -1
  575. package/lib/util/fromTHREE.js +20 -7
  576. package/lib/util/fromTHREE.js.map +1 -1
  577. package/lib/util/geometry.js +42 -29
  578. package/lib/util/geometry.js.map +1 -1
  579. package/lib/util/loadRGBE.js +3 -4
  580. package/lib/util/loadRGBE.js.map +1 -1
  581. package/lib/util/studioLighting.d.ts +18 -0
  582. package/lib/util/studioLighting.js +36 -0
  583. package/lib/util/studioLighting.js.map +1 -0
  584. package/lib/version.d.ts +1 -1
  585. package/lib/version.js +1 -1
  586. package/lib/version.js.map +1 -1
  587. package/llms.txt +5 -0
  588. package/package.json +33 -7
  589. package/lib/renderer/texturesFromMaterials.d.ts +0 -6
  590. package/lib/renderer/texturesFromMaterials.js.map +0 -1
@@ -1,7 +1,28 @@
1
+ import { causticDiagnostics } from './causticDiagnostics.js';
2
+ import { WGPUPhotonMap, photonRadius } from './WGPUPhotonMap.js';
3
+ import { defaultPhotonMapPhotons } from './wgsl/photonMap.compute.js';
4
+ import { researchTransport } from './researchTransport.js';
5
+ import { causticEmissionTable } from './causticEmission.js';
6
+ import { WGPUCausticLightPass } from './WGPUCausticLightPass.js';
7
+ import { causticLightCompute } from './wgsl/causticLight.compute.js';
8
+ import { causticCasterSpheres, hasStochasticCausticCaster, causticCasterSphere, isCausticSpecular } from './caustics.js';
9
+ import { AovPrimitiveCache } from './AovPrimitiveCache.js';
10
+ import { packAovScene } from './aovScene.js';
11
+ import { aovMask } from './aov.js';
12
+ import { proceduralEnvironmentPower } from './proceduralEnvironmentPower.js';
13
+ import { WGPUSkyBaker } from './WGPUSkyBaker.js';
14
+ import { initialSceneConstants, sceneAttributeLayout, sceneTraversalConstants, prepareSceneLightSampling, writeTraceLighting, packSceneAnalyticLights, writeTraceQuality, traceSampling, writeTraceSampling } from './sceneAssembly.js';
15
+ import { rayTraceBindGroupLayouts } from './sceneLayoutSchema.js';
16
+ import { WGPUBindlessMaterialData } from './WGPUBindlessMaterialData.js';
17
+ import { WGPUBindlessTextures } from './WGPUBindlessTextures.js';
18
+ import { bindlessBindGroupLayouts, bindlessShaderSource } from './wgpuBindlessSource.js';
19
+ import { bindlessLimitReason, resourceTableLayout, setResourceTable } from './wgpuBindless.js';
20
+ import { wgpuMaterialConstants } from './wgpuMaterialConstants.js';
21
+ import { wgpuShaderSource, wgpuPipelineConstantKeys, WGPU_SHADER_DEFINE_KEYS } from './wgpuShaderSource.js';
22
+ import { makeRayTraceUniform, WGPU_SCENE_BUFFER_MIN_BYTES } from './wgpuSceneUniform.js';
1
23
  import { constants } from './webgl.js';
2
- import { MATERIAL_FLOAT_COUNT } from './materialLayout.js';
24
+ import { MATERIAL_FLOAT_COUNT } from './materialLayoutSchema.js';
3
25
  import { normalizeBounces } from './pathLimits.js';
4
- import { MATERIAL_TEXTURE_DEFINES, materialTextureFeatures } from './materialTextureSlots.js';
5
26
  import { updateMeshInstanceColors } from './prepareMeshBVH.js';
6
27
  import { updateTLASInstanceColors } from './bvhAccel.js';
7
28
  import { getMaterialHashKey } from '../scene/openPbrSchema.js';
@@ -9,31 +30,31 @@ import { sameInstanceMembership } from './meshInstances.js';
9
30
  import InstancedMesh from '../scene/InstancedMesh.js';
10
31
  import { dirtyMeshInstanceIndices } from './prepareMeshBVH.js';
11
32
  import { cameraApertureRadius } from './cameraLens.js';
12
- import { getRectLightUOrV, getRectLightArea, getSphereLightArea, getSpotLightArea, getSpotLightRadiance, getLightDirection, getRigidLightFrame, validateSceneLights } from './lightsUtil.js';
13
33
  import { getGeometryBvh, setGeometryBvh } from './geometryBvh.js';
34
+ import { packCameraUniform } from './cameraUniform.js';
14
35
  import { SHADOW_CATCHER_ACCUMULATION_BYTES } from './wgsl/chunks/shadowCatcherAccumulation.wgsl.js';
15
- import { samplingPatternCode, zSobolLog2SPP } from './sampling.js';
16
36
  import { DEPTH_SAMPLE_COUNT, packDepthSampleArea, unpackDepthSamples } from './depthQuery.js';
17
37
  import { MeshMembershipCache } from './meshMembership.js';
18
38
  import { mergeRefitRanges } from './bvhAccel.js';
19
39
  import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, packVertexAttributes } from './geometryUpdates.js';
20
40
  import { SceneEnvironmentCache, RectLightTextureTracker } from './sceneLightChanges.js';
21
41
  import { MaterialChangeTracker, materialIsEmissive } from './materialChanges.js';
42
+ import { WGPUEnvMapDistribution } from './WGPUEnvMapDistribution.js';
22
43
  import { WGPUShaderCache, retainedShaderCapacity } from './WGPUShaderCache.js';
23
- import { WGPU_SCENE_FEATURE_BITS, wgpuSceneFeatureFlags } from './WGPUSceneFeatures.js';
44
+ import { WGPUShaderCapabilities } from './WGPUShaderCapabilities.js';
45
+ import { wgpuSceneFeatureFlags } from './WGPUSceneFeatures.js';
46
+ import { lightShaderCapabilities, retainedShaderCapabilities } from './shaderCapabilities.js';
24
47
  import { mat4 } from '../math.js';
25
- import MaterialTable, { DEFAULT_MAX_TEXTURE_ARRAY_BYTES, materialUsesMedium } from './MaterialTable.js';
48
+ import MaterialTable, { DEFAULT_MAX_TEXTURE_ARRAY_BYTES } from './MaterialTable.js';
26
49
  import { numberArraysEqual } from './util.js';
27
- import { generateBackgroundMapFromSceneBackground, environmentRotation, backgroundMapTransform } from './envMapCreation.js';
28
- import rayTraceCompute, { WORKGROUP_SIZE } from './wgsl/rayTrace.compute.js';
29
- import { CONSTANT_DIRECTIONAL_LIGHT, CONSTANT_RECT_LIGHT, CONSTANT_SPHERE_LIGHT, CONSTANT_SPOT_LIGHT } from './lightsUtil.js';
50
+ import { generateBackgroundMapFromSceneBackground, environmentRotation, backgroundMapTransform, backgroundMapRotation } from './envMapCreation.js';
51
+ import { WORKGROUP_SIZE } from './wgsl/rayTrace.compute.js';
30
52
  import { envMapDistribution, environmentMapSourcesMatch } from './envMapDistribution.js';
31
53
  import { updateMeshInstances, updateMeshInstanceTransforms } from './prepareMeshBVH.js';
32
54
  import { buildTLAS, concatBLASBuffers, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
33
55
  import { isTextureDataSource } from '../scene/Texture.js';
34
- import { resolveMaterialAlphaMode } from '../scene/OpenPBRMaterial.js';
35
56
  import { DEFAULT_BEVEL_SAMPLES, DEFAULT_DIRECT_LIGHT_CANDIDATES, normalizeBevelSamples, normalizeDirectLightCandidates } from './RenderingPipeline.js';
36
- import { buildLightAliasTable, EmissiveTriangleCache, LightChoiceBuffer, estimateAnalyticLightPower, estimateEnvironmentPower, LIGHT_CHOICE_ANALYTIC, LIGHT_CHOICE_EMISSIVE_TRIANGLE, LIGHT_CHOICE_ENVIRONMENT, LIGHT_DOMAIN_ENVIRONMENT, LIGHT_DOMAIN_INFINITE, LIGHT_DOMAIN_LOCAL_AND_EMISSIVE, packEmissiveTriangles, packIESProfiles } from './lightSampling.js';
57
+ import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower } from './lightSampling.js';
37
58
  import { getOpenPbrEnergyLutData, OPENPBR_ENERGY_LUT_HEIGHT, OPENPBR_ENERGY_LUT_WIDTH } from './openPbrEnergyLut.js';
38
59
  import { offlineAdaptiveCompute } from './wgsl/offlineAdaptive.compute.js';
39
60
  import { normalizeOfflineAdaptiveSamplingOptions } from './OfflineAdaptiveSampling.js';
@@ -65,6 +86,10 @@ function joinWebGPUErrors(...errors) {
65
86
  .filter(Boolean))
66
87
  ].join('\n');
67
88
  }
89
+ const MAX_TRACE_VARIANTS = 32;
90
+ // Requests are cheap but drivers are not, and a running compilation cannot be cancelled.
91
+ const MAX_CONCURRENT_COMPILES = 2;
92
+ const MAX_QUEUED_COMPILES = 4;
68
93
  function shaderCompilationNow() {
69
94
  return typeof performance === 'undefined' ? Date.now() : performance.now();
70
95
  }
@@ -87,124 +112,29 @@ export function isWebGPUPipelineSpecializationFailure(...errors) {
87
112
  return errors.some((error) => /(?:compute )?library failed creation/i.test(getWebGPUErrorMessage(error)));
88
113
  }
89
114
  /** CPU-side views over the `RayTraceUniform` WGSL struct. */
90
- export function makeRayTraceUniform() {
91
- // Created from https://webgpufundamentals.org/webgpu/lessons/resources/wgsl-offset-computer.html
92
- const RayTraceUniformValues = new ArrayBuffer(256);
93
- const RayTraceUniformViews = {
94
- outputSize: new Float32Array(RayTraceUniformValues, 0, 2),
95
- sampleCount: new Float32Array(RayTraceUniformValues, 8, 1),
96
- scaleOffset: new Float32Array(RayTraceUniformValues, 16, 4),
97
- lightsCount: new Int32Array(RayTraceUniformValues, 32, 1),
98
- transmissiveOutputAlphaMin: new Float32Array(RayTraceUniformValues, 36, 1),
99
- jitter: new Float32Array(RayTraceUniformValues, 40, 2),
100
- backgroundColor: new Float32Array(RayTraceUniformValues, 48, 4),
101
- groundedBackgroundParams: new Float32Array(RayTraceUniformValues, 64, 4),
102
- groundedBackgroundCenter: new Float32Array(RayTraceUniformValues, 80, 2),
103
- blasOffset: new Int32Array(RayTraceUniformValues, 88, 1),
104
- isEmpty: new Int32Array(RayTraceUniformValues, 92, 1),
105
- bevel: new Int32Array(RayTraceUniformValues, 96, 1),
106
- dispersionWavelengths: new Int32Array(RayTraceUniformValues, 100, 1),
107
- envLightPmf: new Float32Array(RayTraceUniformValues, 104, 1),
108
- lightChoiceCount: new Int32Array(RayTraceUniformValues, 108, 1),
109
- lightWeightTotal: new Float32Array(RayTraceUniformValues, 112, 1),
110
- lightSamplingTextureWidth: new Int32Array(RayTraceUniformValues, 116, 1),
111
- lightChoiceTexelOffset: new Int32Array(RayTraceUniformValues, 120, 1),
112
- bounces: new Int32Array(RayTraceUniformValues, 124, 1),
113
- varianceMoments: new Int32Array(RayTraceUniformValues, 128, 1),
114
- adaptiveSampling: new Int32Array(RayTraceUniformValues, 132, 1),
115
- clamping: new Float32Array(RayTraceUniformValues, 136, 1),
116
- directLightCandidates: new Int32Array(RayTraceUniformValues, 140, 1),
117
- backgroundFlags: new Int32Array(RayTraceUniformValues, 144, 1),
118
- loopUnrollGuard: new Uint32Array(RayTraceUniformValues, 148, 1),
119
- sceneFeatureFlags: new Uint32Array(RayTraceUniformValues, 152, 1),
120
- sequenceSeed: new Uint32Array(RayTraceUniformValues, 156, 1),
121
- zSobolLogSPP: new Uint32Array(RayTraceUniformValues, 160, 1),
122
- sampleSequenceOffset: new Uint32Array(RayTraceUniformValues, 164, 1),
123
- environmentRotation: new Float32Array(RayTraceUniformValues, 176, 16),
124
- backgroundMapTransform: new Float32Array(RayTraceUniformValues, 240, 4)
125
- };
126
- // Fixed runtime trip bounds discourage thin-film loop unrolling. Keep 4 for
127
- // 2 polarizations/substrates and 3 channels, independent of BSDF budgets.
128
- RayTraceUniformViews.loopUnrollGuard[0] = 4;
129
- RayTraceUniformViews.environmentRotation.set(environmentRotation([]));
130
- RayTraceUniformViews.backgroundMapTransform.set(backgroundMapTransform());
131
- RayTraceUniformViews.bevel[0] = 1;
132
- RayTraceUniformViews.dispersionWavelengths[0] = 3;
133
- return {
134
- buffer: RayTraceUniformValues,
135
- views: RayTraceUniformViews
136
- };
137
- }
115
+ export { makeRayTraceUniform } from './wgpuSceneUniform.js';
138
116
  export function packAnalyticsLightsToBuffer(lights, texturesIndices, selectionPmfs, iesProfileTexelOffsets = []) {
139
- validateSceneLights(lights);
140
- const numLights = lights.length;
141
- if (numLights > FIXED_LIGHT_COUNT) {
142
- console.warn(`Too many lights, only the first ${FIXED_LIGHT_COUNT} lights will be used.`);
143
- }
144
- const data = new Float32Array(numLights * PIXELS_PER_LIGHT * 4);
145
- function packVec3(idx, v) {
146
- for (let i = 0; i < 3; i++) {
147
- data[i + idx * 4] = v[i];
148
- }
149
- }
150
- const lightTypeConstants = {
151
- DirectionalLight: CONSTANT_DIRECTIONAL_LIGHT,
152
- SphereLight: CONSTANT_SPHERE_LIGHT,
153
- RectLight: CONSTANT_RECT_LIGHT,
154
- SpotLight: CONSTANT_SPOT_LIGHT
155
- };
156
- for (let i = 0; i < numLights; i++) {
157
- const light = lights[i];
158
- const color = light.color;
159
- const intensity = light.intensity;
160
- const off = i * PIXELS_PER_LIGHT;
161
- packVec3(off, light.position);
162
- data[off * 4 + 3] = lightTypeConstants[light.type];
163
- // emission
164
- const emissionScale = light.type === 'SpotLight' ? getSpotLightRadiance(light) : intensity;
165
- packVec3(off + 1, [
166
- color[0] * emissionScale,
167
- color[1] * emissionScale,
168
- color[2] * emissionScale
169
- ]);
170
- if (light.type === 'RectLight') {
171
- data[(off + 1) * 4 + 3] = getRectLightArea(light);
172
- packVec3(off + 2, getRectLightUOrV(light, 0));
173
- data[(off + 2) * 4 + 3] = light.spread;
174
- packVec3(off + 3, getRectLightUOrV(light, 1));
175
- }
176
- else if (light.type === 'SphereLight') {
177
- data[(off + 1) * 4 + 3] = getSphereLightArea(light);
178
- data[(off + 2) * 4 + 3] = light.radius;
179
- }
180
- else if (light.type === 'SpotLight') {
181
- data[(off + 1) * 4 + 3] = getSpotLightArea(light);
182
- packVec3(off + 2, getLightDirection(light));
183
- data[(off + 2) * 4 + 3] = light.radius;
184
- packVec3(off + 3, [
185
- Math.cos((light.outerAngle * Math.PI) / 180),
186
- Math.cos((Math.min(light.innerAngle, light.outerAngle) * Math.PI) / 180),
187
- 0
188
- ]);
189
- }
190
- else if (light.type === 'DirectionalLight') {
191
- packVec3(off + 2, getLightDirection(light));
192
- }
193
- data[(off + 3) * 4 + 3] = light.type === 'SpotLight'
194
- ? iesProfileTexelOffsets[i] ?? -1
195
- : texturesIndices[i] ?? -1;
196
- data[(off + 4) * 4] = selectionPmfs[i] ?? 0;
197
- packVec3(off + 5, getRigidLightFrame(light).x);
198
- }
199
- return data;
117
+ return packSceneAnalyticLights(lights, texturesIndices, selectionPmfs, i => iesProfileTexelOffsets[i]);
200
118
  }
201
119
  class WGPURayTracePass {
120
+ _textureSelection;
121
+ _aovPrimitiveCache = new AovPrimitiveCache();
202
122
  _device;
203
123
  _rayTracePipeline;
204
124
  _targetAspect = 1;
205
125
  _width = 0;
206
126
  _height = 0;
207
127
  _meshes = [];
128
+ _causticKey = '';
129
+ _photonMapping = false;
130
+ _photonMap;
131
+ _causticLight;
132
+ _causticSettings;
133
+ _causticPerspective = true;
134
+ _causticCameraScale = 1;
135
+ _causticBounds = [0, 0, 0, 0];
136
+ _transport;
137
+ _lightTracing = false;
208
138
  _sceneEnvironment = new SceneEnvironmentCache();
209
139
  _rectLightTextures = new RectLightTextureTracker();
210
140
  _analyticLights = [];
@@ -212,6 +142,9 @@ class WGPURayTracePass {
212
142
  _materialChanges = new MaterialChangeTracker();
213
143
  _pendingMaterialUpdate;
214
144
  _materialData;
145
+ _bindless;
146
+ _retiredBindless = new Set();
147
+ _disposed = false;
215
148
  _maxTextureArrayBytes = DEFAULT_MAX_TEXTURE_ARRAY_BYTES;
216
149
  _pendingGeometryUpdates = new Set();
217
150
  _needsUpdateMeshData = true;
@@ -236,6 +169,8 @@ class WGPURayTracePass {
236
169
  if (!output)
237
170
  return;
238
171
  this._targetOutputs.delete(target);
172
+ this._causticLight?.release(target);
173
+ this._photonMap?.release(target);
239
174
  for (const buffer of output.buffers)
240
175
  buffer.destroy();
241
176
  if (this._activeTarget === target) {
@@ -244,17 +179,23 @@ class WGPURayTracePass {
244
179
  }
245
180
  }
246
181
  getTargetMemoryBytes(target) {
247
- return this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0;
182
+ return ((this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0) +
183
+ (this._causticLight?.bytes(target) ?? 0) +
184
+ (this._photonMap?.bytes(target) ?? 0));
248
185
  }
249
186
  useTarget(target) {
187
+ if (this._photonMapping && this._activeTarget !== target)
188
+ this._needsPhotonTargetBindGroups = true;
250
189
  this._activeTarget = target;
251
190
  const output = this._targetOutputs.get(target);
252
- [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] = output?.buffers ?? [];
191
+ [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
192
+ output?.buffers ?? [];
253
193
  if (output?.bindGroup && output.pipeline === this._rayTracePipeline && this._rayTraceBindGroups) {
254
- this._rayTraceBindGroups[3] = output.bindGroup;
194
+ this._rayTraceBindGroups[this._bindless ? 2 : 3] = output.bindGroup;
195
+ this._needsUpdateOutputBindGroup = false;
255
196
  }
256
197
  else {
257
- this._needsUpdateBindGroups = true;
198
+ this._needsUpdateOutputBindGroup = true;
258
199
  }
259
200
  }
260
201
  _outColorBuffer;
@@ -281,6 +222,7 @@ class WGPURayTracePass {
281
222
  _instancesBuffer;
282
223
  _emissiveCache = new EmissiveTriangleCache();
283
224
  _lightChoices = new LightChoiceBuffer();
225
+ _shadowCatcherLightChoices = new LightChoiceBuffer();
284
226
  _lightSamplingData;
285
227
  _lightSamplingScratch;
286
228
  _lightSamplingTriangleCount = -1;
@@ -296,26 +238,29 @@ class WGPURayTracePass {
296
238
  _envImageSource;
297
239
  _envTex;
298
240
  _envDistributionTex;
241
+ _envDistribution;
242
+ _skyBaker;
299
243
  _backgroundImageSource;
300
244
  _backgroundTex;
301
- _rayTracePipelineLayout;
302
245
  _rayTraceBindGroups;
246
+ _photonEmissionGroups;
303
247
  _blasLayoutMask = 1;
304
- _compiledBlasLayoutMask = 0;
305
- _compiledAnisotropyDirections = false;
306
- _compiledCoatNormals = false;
307
- _compiledHasAlpha = true;
308
- _compiledSceneHasAlpha = true;
309
248
  _alphaBlendStrategy = 'stochastic';
310
- _constants = {
311
- USE_BACKGROUND: false,
312
- USE_BACKGROUND_MAP: false,
313
- USE_GROUNDED_BACKGROUND: false,
314
- SAMPLING_PATTERN: 0,
315
- BEVEL_SAMPLES: DEFAULT_BEVEL_SAMPLES
316
- };
317
- _prevPipelineConstants = {};
318
- _prevDefinesCode = '';
249
+ _constants = initialSceneConstants();
250
+ _retainedShaderCapabilities;
251
+ _retainedShaderStructure;
252
+ _shaderCapabilities = new WGPUShaderCapabilities();
253
+ _targetTransport = new WeakMap();
254
+ _traceVariants = new Map();
255
+ _queuedCompiles = [];
256
+ _activeCompiles = 0;
257
+ _traceModules = new Map();
258
+ _requestedVariant;
259
+ _activeVariant;
260
+ _activeInputs;
261
+ _awaitingRetirement;
262
+ _resourceError;
263
+ _aovPipelinesFailed = false;
319
264
  _hasTransmission = false;
320
265
  _hasShadowCatcher = false;
321
266
  _hasDisplacement = false;
@@ -330,21 +275,26 @@ class WGPURayTracePass {
330
275
  _apertureRadius = 0;
331
276
  _focusDistance = 10;
332
277
  _needsUpdateBindGroups = true;
333
- _computeShaderModule;
278
+ // Photon maps are per target. Switching targets only rebinds them, which
279
+ // must not send steady frames through the asynchronous pipeline path.
280
+ _needsPhotonTargetBindGroups = false;
281
+ // Target selection is not a scene-resource edit. A stale fallback must not
282
+ // leave global bindings dirty after switching back to a prepared target.
283
+ _needsUpdateOutputBindGroup = false;
284
+ _aovPipelines = new WeakMap();
334
285
  _depthCompute;
335
286
  _depthTracePipeline;
336
287
  _depthPipeline;
337
288
  _shaderCache;
338
289
  _debugBVHBuffer;
339
- _isCompilingShader = false;
340
- _shaderCompilationDiagnostics;
341
- _pipelineError;
342
290
  // Fold scene invariants before backend inlining; runtime edits use uniforms.
343
291
  _bakePipelineConstants = true;
344
- _failedPipelineSignature;
345
- constructor(device) {
292
+ constructor(device, _textureSelection = { requested: 'atlas' }) {
293
+ this._textureSelection = _textureSelection;
346
294
  this._device = device;
347
295
  this._shaderCache = new WGPUShaderCache(device);
296
+ if (_textureSelection.support)
297
+ this._bindless = new WGPUBindlessTextures(device);
348
298
  this._uniforms = {
349
299
  cameraUniform: this._uniformBuffer(36 * 4),
350
300
  rtUniform: this._uniformBuffer(this._rtUniform.buffer.byteLength),
@@ -382,8 +332,14 @@ class WGPURayTracePass {
382
332
  device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, new Float32Array([Number.MAX_SAFE_INTEGER]), { bytesPerRow: 4 }, [1, 1]);
383
333
  }
384
334
  getOutputBuffer() {
335
+ // Another render may have traced since this target's samples were written.
336
+ const transport = this._targetTransport.get(this._activeTarget) ?? 0;
385
337
  return {
386
338
  color: this._outColorBuffer,
339
+ ...(transport & 2 || ((this._transport?.debugGroup ?? -1) >= 0 || causticDiagnostics.debugGroup >= 0) ? { wideHdr: true } : {}),
340
+ ...(transport & 1 && this._causticLight?.get(this._activeTarget)
341
+ ? { caustic: this._causticLight.get(this._activeTarget) }
342
+ : {}),
387
343
  normal: this._outNormalBuffer,
388
344
  albedo: this._outAlbedoBuffer,
389
345
  shadowCatcher: this._outShadowCatcherBuffer
@@ -578,130 +534,28 @@ class WGPURayTracePass {
578
534
  varianceBuffer.destroy();
579
535
  }
580
536
  }
581
- _createPipelineLayout() {
582
- //Bind group layouts
583
- const commonBufferLayoutEntries = Array.from({ length: 4 }, (_, i) => ({
584
- binding: i,
585
- visibility: GPUShaderStage.COMPUTE,
586
- buffer: {
587
- type: 'read-only-storage'
588
- }
589
- }));
590
- const buffersBindGroupLayout = this._shaderCache.createBindGroupLayout({
591
- label: 'Buffers',
592
- entries: commonBufferLayoutEntries
593
- });
594
- const textureLayoutEntries = [
595
- {
596
- binding: 0,
597
- visibility: GPUShaderStage.COMPUTE,
598
- sampler: {}
599
- },
600
- {
601
- binding: 1,
602
- visibility: GPUShaderStage.COMPUTE,
603
- texture: {
604
- viewDimension: '2d-array'
605
- }
606
- },
607
- // Map size
608
- {
609
- binding: 3,
610
- visibility: GPUShaderStage.COMPUTE,
611
- buffer: {
612
- type: 'uniform'
613
- }
614
- },
615
- // Env map
616
- {
617
- binding: 4,
618
- visibility: GPUShaderStage.COMPUTE,
619
- texture: {
620
- viewDimension: '2d',
621
- sampleType: 'unfilterable-float'
622
- }
623
- },
624
- // Env map distribution
625
- {
626
- binding: 5,
627
- visibility: GPUShaderStage.COMPUTE,
628
- texture: {
629
- viewDimension: '2d',
630
- sampleType: 'unfilterable-float'
631
- }
632
- },
633
- // Background map
634
- {
635
- binding: 6,
636
- visibility: GPUShaderStage.COMPUTE,
637
- texture: {
638
- viewDimension: '2d',
639
- sampleType: 'unfilterable-float'
640
- }
641
- },
642
- {
643
- binding: 8,
644
- visibility: GPUShaderStage.COMPUTE,
645
- texture: {
646
- viewDimension: '2d',
647
- sampleType: 'unfilterable-float'
648
- }
649
- },
650
- {
651
- binding: 9,
652
- visibility: GPUShaderStage.COMPUTE,
653
- texture: {
654
- viewDimension: '2d',
655
- sampleType: 'unfilterable-float'
656
- }
657
- },
658
- {
659
- binding: 10,
660
- visibility: GPUShaderStage.COMPUTE,
661
- texture: {
662
- viewDimension: '2d',
663
- sampleType: 'unfilterable-float'
664
- }
665
- }
666
- ];
667
- const texturesBindGroupLayout = this._shaderCache.createBindGroupLayout({
668
- label: 'Textures', entries: textureLayoutEntries
669
- });
670
- const uniformsBindGroupLayout = this._shaderCache.createBindGroupLayout({
671
- label: 'Uniforms',
672
- entries: [
673
- ...Array.from({ length: Object.keys(this._uniforms).length }, (_, i) => ({
674
- binding: i,
675
- visibility: GPUShaderStage.COMPUTE,
676
- buffer: {
677
- type: 'uniform'
678
- }
679
- })),
680
- ...([])
681
- ]
682
- });
683
- const rayTraceOutBuffersBindGroupLayout = this._shaderCache.createBindGroupLayout({
684
- label: 'Ray Trace Out Buffers',
685
- entries: [
686
- ...Array.from({ length: 4 }, (_, i) => ({
687
- binding: i,
688
- visibility: GPUShaderStage.COMPUTE,
689
- buffer: { type: 'storage' }
690
- })),
691
- ...([])
692
- ]
693
- });
694
- this._rayTracePipelineLayout = this._shaderCache.createPipelineLayout({
537
+ _createPipelineLayout(bindless, photonMapping) {
538
+ const descriptor = {
695
539
  label: 'Ray Trace Pipeline Layout',
696
- bindGroupLayouts: [
697
- buffersBindGroupLayout,
698
- texturesBindGroupLayout,
699
- uniformsBindGroupLayout,
700
- rayTraceOutBuffersBindGroupLayout
701
- ]
702
- });
540
+ bindGroupLayouts: (bindless ? bindlessBindGroupLayouts() : rayTraceBindGroupLayouts()).map((layout, index) => {
541
+ if (photonMapping && index === (bindless ? 0 : 1))
542
+ layout = {
543
+ ...layout,
544
+ entries: [
545
+ ...layout.entries,
546
+ ...[11, 12].map((binding) => ({
547
+ binding: binding + (bindless ? 4 : 0),
548
+ visibility: GPUShaderStage.COMPUTE,
549
+ texture: { sampleType: 'uint' }
550
+ }))
551
+ ]
552
+ };
553
+ return this._shaderCache.createBindGroupLayout(layout);
554
+ })
555
+ };
556
+ return this._shaderCache.createPipelineLayout(bindless ? resourceTableLayout(descriptor) : descriptor);
703
557
  }
704
- _updateGPUBuffer(label, gpuBuffer, data, minSize = 200) {
558
+ _updateGPUBuffer(label, gpuBuffer, data, minSize = WGPU_SCENE_BUFFER_MIN_BYTES) {
705
559
  const device = this._device;
706
560
  const size = Math.max(data.byteLength, minSize);
707
561
  if (gpuBuffer == null || gpuBuffer.size < size) {
@@ -727,17 +581,45 @@ class WGPURayTracePass {
727
581
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
728
582
  });
729
583
  }
730
- async updatePipeline() {
731
- if (this._isCompilingShader) {
584
+ _preparePhotonTarget() {
585
+ if (!this._photonMapping)
732
586
  return;
587
+ this._photonMap ??= new WGPUPhotonMap(this._device);
588
+ const before = this._photonMap.get(this._activeTarget)?.data;
589
+ const requested = this._causticSettings?.photonMapping;
590
+ const n = typeof requested === 'object' ? requested.photonsPerSample : undefined;
591
+ const after = this._photonMap.ensure(this._activeTarget, Number.isFinite(n) && n > 0 ? n : defaultPhotonMapPhotons(this._width, this._height, causticDiagnostics.photonCountDivisor));
592
+ if (before !== after.data)
593
+ this._needsUpdateBindGroups = true;
594
+ }
595
+ /**
596
+ * Prepares the pipeline for the settings currently written to this pass.
597
+ * Everything except compilation runs synchronously, and compilation only
598
+ * writes to its own variant. Renders with different settings can therefore
599
+ * interleave without invalidating each other. Returns a promise while the
600
+ * requested state waits on the GPU; undefined means nothing is outstanding.
601
+ */
602
+ updatePipeline() {
603
+ if (this._disposed)
604
+ return;
605
+ if (this._needsUpdateMeshData || this._needsUpdateMeshMembership || this._pendingGeometryUpdates.size ||
606
+ this._needsRefitTLAS || this._dirtyColorMeshes.size || this._pendingMaterialUpdate || this._needsUpdateLightSamplingData) {
607
+ // A failure can have the same shader signature as its repaired scene.
608
+ // Retry after authored updates, while keeping an unchanged failure idle.
609
+ this._resourceError = undefined;
610
+ for (const [signature, variant] of this._traceVariants)
611
+ if (variant.status === 'failed')
612
+ this._traceVariants.delete(signature);
613
+ if (this._aovPipelinesFailed) {
614
+ this._aovPipelines = new WeakMap();
615
+ this._aovPipelinesFailed = false;
616
+ }
733
617
  }
734
- // Set this before any synchronous preparation. updatePipeline is called
735
- // without awaiting it, so a preparation error must not let createPass run
736
- // with an undefined pipeline in the same frame.
737
- this._isCompilingShader = true;
738
- let attemptedPipelineSignature;
739
- let compilationStartedAt;
740
- let pipelineCompilationStartedAt;
618
+ this._awaitingRetirement = this._bindless?.retirement;
619
+ if (this._awaitingRetirement)
620
+ return this._awaitingRetirement;
621
+ if (this._resourceError)
622
+ return;
741
623
  try {
742
624
  if (this._needsUpdateMeshData) {
743
625
  this._updateMeshData();
@@ -753,314 +635,420 @@ class WGPURayTracePass {
753
635
  this._updateMeshColors();
754
636
  if (this._pendingMaterialUpdate)
755
637
  this._updateMaterialData();
638
+ this._setCaustics(this._causticSettings);
756
639
  if (this._needsUpdateLightSamplingData) {
757
640
  this._updateLightSamplingData();
758
641
  }
759
- const device = this._device;
760
- const constants = this._constants;
761
- this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(constants);
762
- const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) &&
763
- this._alphaBlendStrategy === 'deterministic-source-over';
642
+ this._setCaustics(this._causticSettings);
643
+ if (this._lightTracing || this._photonMapping)
644
+ this._causticBounds = causticCasterSphere(this._meshes);
645
+ this._targetTransport.set(this._activeTarget, (+this._lightTracing) | (+this._photonMapping << 1));
646
+ this._preparePhotonTarget();
647
+ const pipelineInputs = JSON.stringify([
648
+ this._constants,
649
+ this._hasDisplacement,
650
+ this._alphaBlendStrategy,
651
+ this._blasLayoutMask,
652
+ this._bakePipelineConstants,
653
+ this._causticKey,
654
+ !!this._bindless
655
+ ]);
656
+ // Steady rendering must not repeat capability selection and cache-key
657
+ // preparation. Dirty resources still take the normal bind-group path.
658
+ if (pipelineInputs === this._activeInputs &&
659
+ this._rayTracePipeline &&
660
+ this._rayTraceBindGroups &&
661
+ !this._needsUpdateBindGroups) {
662
+ if (this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
663
+ this._needsPhotonTargetBindGroups = false;
664
+ this._needsUpdateOutputBindGroup = false;
665
+ this._updateBindGroups();
666
+ }
667
+ this._requestedVariant = this._activeVariant;
668
+ return;
669
+ }
670
+ this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(this._constants);
671
+ const requiredCapabilities = retainedShaderCapabilities(this._constants);
672
+ const constants = { ...this._constants, ...requiredCapabilities };
673
+ const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) && this._alphaBlendStrategy === 'deterministic-source-over';
764
674
  const hasSceneAlpha = Boolean(constants.HAS_ALPHA);
765
- const pipelineSignature = JSON.stringify({
766
- constants: Object.keys(constants)
767
- .sort()
768
- .map((key) => [key, constants[key]]),
675
+ const pipelineConstantsKeys = wgpuPipelineConstantKeys(constants);
676
+ const defineCode = WGPU_SHADER_DEFINE_KEYS.map((key) => `const ${key} = ${constants[key]};`).join('\n');
677
+ const structure = {
769
678
  hasDisplacement: this._hasDisplacement,
679
+ caustics: this._causticKey,
680
+ bindless: !!this._bindless,
770
681
  hasAlpha,
771
682
  hasSceneAlpha,
772
683
  hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
773
684
  hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
774
685
  blasLayoutMask: this._blasLayoutMask,
775
686
  bakePipelineConstants: this._bakePipelineConstants
687
+ };
688
+ const structureKey = JSON.stringify({
689
+ ...structure,
690
+ defineCode,
691
+ constants: pipelineConstantsKeys
692
+ .filter((key) => !(key in requiredCapabilities))
693
+ .map((key) => [key, constants[key]])
694
+ });
695
+ // Prefer an existing compatible pipeline, including an earlier lean
696
+ // variant. Only grow the current variant when it cannot cover an edit;
697
+ // do not carry unused capabilities across different transport/layouts.
698
+ const capabilities = this._shaderCapabilities.find(structureKey, requiredCapabilities) ??
699
+ retainedShaderCapabilities(this._constants, structureKey === this._retainedShaderStructure ? this._retainedShaderCapabilities : undefined);
700
+ Object.assign(constants, capabilities);
701
+ // Runtime feature values live in uniforms, so edits can share a pipeline
702
+ // as long as its compiled defines and overrides are the same.
703
+ const signature = JSON.stringify({
704
+ ...structure,
705
+ defineCode,
706
+ constants: pipelineConstantsKeys.map((key) => [key, constants[key]])
776
707
  });
777
- attemptedPipelineSignature = pipelineSignature;
778
- if (pipelineSignature === this._failedPipelineSignature) {
708
+ const variant = this._traceVariants.get(signature) ??
709
+ this._startTraceVariant(signature, constants, { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys, structureKey, capabilities });
710
+ this._requestedVariant = variant;
711
+ if (variant.status === 'pending') {
712
+ this._requestQueuedCompile(variant);
713
+ return variant.settled;
714
+ }
715
+ if (variant.status === 'failed')
779
716
  return;
717
+ this._installTraceVariant(variant, pipelineInputs);
718
+ }
719
+ catch (error) {
720
+ this._resourceError = error;
721
+ this._needsUpdateBindGroups = true;
722
+ console.error('Failed to update WebGPU ray tracing resources.', error);
723
+ }
724
+ }
725
+ /** Runs only for a ready variant, without yielding, so tracing can start in this call. */
726
+ _installTraceVariant(variant, pipelineInputs) {
727
+ if (this._activeVariant !== variant) {
728
+ this._activeVariant = variant;
729
+ this._rayTracePipeline = variant.pipeline;
730
+ this._depthCompute = variant.depthCompute;
731
+ this._needsUpdateBindGroups = true;
732
+ }
733
+ const emission = () => causticEmissionTable(this._analyticLights, this._emissiveTriangles, this._causticBounds[3], this._envImageSource && 'sky' in this._envImageSource
734
+ ? proceduralEnvironmentPower(this._envImageSource)
735
+ : estimateEnvironmentPower(this._envImageSource));
736
+ const casters = () => causticDiagnostics.aggregateCasters ? [this._causticBounds] : causticCasterSpheres(this._meshes);
737
+ if (this._lightTracing && this._causticLight) {
738
+ this._causticLight.setDistribution(casters(), emission());
739
+ this._causticLight.use(variant.lightEmitter);
740
+ }
741
+ if (this._photonMapping && this._photonMap) {
742
+ this._photonMap.emission.setDistribution(casters(), emission());
743
+ this._photonMap.emission.use(variant.photonEmitter);
744
+ }
745
+ // TODO rebind when lights, materials, bvh changed.
746
+ if (this._needsUpdateBindGroups || this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
747
+ this._needsUpdateBindGroups = false;
748
+ this._needsPhotonTargetBindGroups = false;
749
+ this._needsUpdateOutputBindGroup = false;
750
+ this._updateBindGroups();
751
+ }
752
+ this._retainedShaderCapabilities = variant.capabilities;
753
+ this._retainedShaderStructure = variant.structureKey;
754
+ this._shaderCapabilities.remember(variant.structureKey, variant.capabilities);
755
+ this._activeInputs = pipelineInputs;
756
+ }
757
+ /** Snapshots everything compilation needs. The task must not read mutable pass state afterwards. */
758
+ _startTraceVariant(signature, constants, shader) {
759
+ const bake = this._bakePipelineConstants, bindless = this._bindless;
760
+ const hasDisplacement = this._hasDisplacement, blasLayoutMask = this._blasLayoutMask;
761
+ const lightTracing = this._lightTracing, photonMapping = this._photonMapping;
762
+ const causticKey = this._causticKey, transport = this._transport;
763
+ const { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys } = shader;
764
+ const emitterSource = (mode) => {
765
+ const code = causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode, '', causticDiagnostics.fullEmissionRandom !== false);
766
+ return bindless ? bindlessShaderSource(code, this._textureSelection.support.enable) : code;
767
+ };
768
+ const lightEmitter = lightTracing ? this._causticLight ??= new WGPUCausticLightPass(this._device) : undefined;
769
+ const photonEmitter = photonMapping ? this._photonMap?.emission : undefined;
770
+ const pipelineDiagnostic = {
771
+ label: 'Ray Trace', entryPoint: 'main', firstFrameRequired: true, status: 'compiling',
772
+ startedAtMs: 0, concurrentCompilationCount: 1
773
+ };
774
+ const variant = {
775
+ status: 'pending', capabilities: shader.capabilities, structureKey: shader.structureKey,
776
+ settled: undefined,
777
+ diagnostics: {
778
+ status: 'compiling', sourceBytes: 0, sourceLines: 0, pipelineCount: 1,
779
+ completedPipelineCount: 0, sourceGenerationTimeMs: 0, moduleCreationTimeMs: 0,
780
+ modules: [], pipelines: [{ ...pipelineDiagnostic }], maximumConcurrentPipelineCount: 1, peakConcurrentPipelineCount: 1
780
781
  }
781
- this._pipelineError = undefined;
782
- // TODO hardcoded defines that will be replaced in the code.
783
- const definesKeys = [
784
- 'NUM_MAPS',
785
- 'TLAS_STACK_SIZE',
786
- 'BLAS_STACK_SIZE',
787
- 'NUM_UNIFORM_LIGHTS'
788
- ];
789
- // Scene feature flags and background selection are runtime uniforms so
790
- // interactive edits do not recompile the path tracer.
791
- const constantsNotUsed = [
792
- ...Object.keys(WGPU_SCENE_FEATURE_BITS),
793
- 'USE_BACKGROUND',
794
- 'USE_BACKGROUND_MAP',
795
- 'USE_GROUNDED_BACKGROUND',
796
- ...MATERIAL_TEXTURE_DEFINES,
797
- 'USE_ANISOTROPY_DIRECTIONS',
798
- 'USE_COAT_NORMAL_FRAMES',
799
- 'HAS_ALPHA_BLEND',
800
- 'HAS_ALPHA_MASK'
801
- ];
802
- const defines = definesKeys.reduce((obj, key) => {
803
- obj[key] = constants[key];
804
- return obj;
805
- }, {});
806
- const pipelineConstantsKeys = Object.keys(constants)
807
- .sort()
808
- .filter((key) => !definesKeys.includes(key) && !constantsNotUsed.includes(key));
809
- const defineCode = definesKeys
810
- .map((key) => `const ${key} = ${defines[key]};`)
811
- .join('\n');
812
- const previousPipelineConstantsKeys = Object.keys(this._prevPipelineConstants)
813
- .filter((key) => !definesKeys.includes(key) && !constantsNotUsed.includes(key))
814
- .sort();
815
- const needsCreateShaderModule = !this._rayTracePipelineLayout ||
816
- !this._computeShaderModule ||
817
- pipelineConstantsKeys.join(',') !==
818
- previousPipelineConstantsKeys.join(',') ||
819
- defineCode !== this._prevDefinesCode ||
820
- hasAlpha !== this._compiledHasAlpha ||
821
- hasSceneAlpha !== this._compiledSceneHasAlpha ||
822
- Boolean(constants.USE_ANISOTROPY_DIRECTIONS) !== this._compiledAnisotropyDirections ||
823
- Boolean(constants.USE_COAT_NORMAL_FRAMES) !== this._compiledCoatNormals ||
824
- this._blasLayoutMask !== this._compiledBlasLayoutMask ||
825
- (this._bakePipelineConstants &&
826
- pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]));
827
- const needsCreatePipeline = needsCreateShaderModule ||
828
- pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]);
829
- compilationStartedAt = needsCreatePipeline
830
- ? shaderCompilationNow()
831
- : undefined;
832
- let sourceGenerationTimeMs = 0;
833
- let moduleCreationTimeMs = 0;
834
- let sourceBytes = this._shaderCompilationDiagnostics?.sourceBytes ?? 0;
835
- let sourceLines = this._shaderCompilationDiagnostics?.sourceLines ?? 0;
836
- let sourceFingerprint = this._shaderCompilationDiagnostics?.modules[0]?.sourceFingerprint ?? '';
837
- if (needsCreateShaderModule) {
838
- const sourceGenerationStartedAt = shaderCompilationNow();
839
- const constantsCode = pipelineConstantsKeys
840
- .map((key) => `${this._bakePipelineConstants ? 'const' : 'override'} ${key} = ${constants[key].toString()};`)
841
- .join('\n');
842
- const rayTraceShaderCode = /* wgsl */ `
843
- ${defineCode}
844
- ${constantsCode}
845
- ${rayTraceCompute({
846
- hasDisplacement: this._hasDisplacement,
782
+ };
783
+ const device = this._device, groupIndices = bindless ? [0, 1] : [0, 1, 2];
784
+ let startedAt = 0;
785
+ const compile = async () => {
786
+ startedAt = shaderCompilationNow();
787
+ // Override values are not part of the WGSL unless they are baked, so a
788
+ // quality edit can share the module that the driver has already parsed.
789
+ const moduleKey = JSON.stringify([causticKey, !!bindless, hasAlpha, hasSceneAlpha, hasDisplacement,
790
+ constants.USE_ANISOTROPY_DIRECTIONS, constants.USE_COAT_NORMAL_FRAMES, blasLayoutMask, defineCode, pipelineConstantsKeys,
791
+ bake && pipelineConstantsKeys.map((key) => constants[key])]);
792
+ let source = this._traceModules.get(moduleKey);
793
+ if (!source) {
794
+ let code = wgpuShaderSource(constants, {
795
+ hasDisplacement,
847
796
  hasAlpha,
848
797
  hasSceneAlpha,
798
+ ...transport?.features,
799
+ lightTracing,
800
+ photonMapping,
801
+ photonMaxDepth: causticDiagnostics.photonMaxDepth ?? 20,
802
+ photonDirectSubset: causticDiagnostics.photonDirectSubset,
803
+ debugGroup: causticDiagnostics.debugGroup,
804
+ workgroupSize: transport?.workgroupSize ?? WORKGROUP_SIZE,
849
805
  hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
850
806
  hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
851
- blasLayoutMask: this._blasLayoutMask
852
- })}`;
853
- sourceBytes = rayTraceShaderCode.length;
854
- sourceLines = shaderSourceLineCount(rayTraceShaderCode);
855
- sourceFingerprint = shaderSourceFingerprint(rayTraceShaderCode);
856
- sourceGenerationTimeMs =
857
- shaderCompilationNow() - sourceGenerationStartedAt;
858
- const moduleCreationStartedAt = shaderCompilationNow();
859
- this._computeShaderModule = this._shaderCache.createShaderModule({
860
- label: 'Ray Trace',
861
- code: rayTraceShaderCode
862
- });
863
- moduleCreationTimeMs =
864
- shaderCompilationNow() - moduleCreationStartedAt;
865
- this._compiledHasAlpha = hasAlpha;
866
- this._compiledSceneHasAlpha = hasSceneAlpha;
867
- this._compiledAnisotropyDirections = Boolean(constants.USE_ANISOTROPY_DIRECTIONS);
868
- this._compiledCoatNormals = Boolean(constants.USE_COAT_NORMAL_FRAMES);
869
- this._compiledBlasLayoutMask = this._blasLayoutMask;
807
+ blasLayoutMask,
808
+ dynamicEnvironment: true
809
+ }, bake);
810
+ if (bindless)
811
+ code = bindlessShaderSource(code, this._textureSelection.support.enable);
812
+ variant.diagnostics.sourceGenerationTimeMs = shaderCompilationNow() - startedAt;
813
+ const moduleStartedAt = shaderCompilationNow();
814
+ source = {
815
+ module: this._shaderCache.createShaderModule({ label: 'Ray Trace', code }),
816
+ bytes: code.length, lines: shaderSourceLineCount(code), fingerprint: shaderSourceFingerprint(code)
817
+ };
818
+ variant.diagnostics.moduleCreationTimeMs = shaderCompilationNow() - moduleStartedAt;
819
+ this._traceModules.set(moduleKey, source);
820
+ if (this._traceModules.size > 8)
821
+ this._traceModules.delete(this._traceModules.keys().next().value);
870
822
  }
871
- if (needsCreatePipeline) {
872
- if (!this._rayTracePipelineLayout) {
873
- device.pushErrorScope('validation');
874
- let layoutScopeOpen = true;
875
- try {
876
- this._createPipelineLayout();
877
- const layoutError = await device.popErrorScope();
878
- layoutScopeOpen = false;
879
- if (layoutError)
880
- throw layoutError;
881
- }
882
- catch (error) {
883
- const scopedError = layoutScopeOpen
884
- ? await device.popErrorScope().catch(() => null)
885
- : null;
886
- this._rayTracePipelineLayout = undefined;
887
- const detail = joinWebGPUErrors(error, scopedError);
888
- throw new Error(`Unable to create the ray tracing pipeline layout: ${detail}`);
889
- }
890
- }
891
- // NOTE must before creating pipeline.
892
- // Because createComputePipelineAsync is async.
893
- // The constants may be changed after the pipeline is created(in setMesh).
894
- this._prevPipelineConstants = { ...this._constants };
895
- this._prevDefinesCode = defineCode;
896
- this._needsUpdateBindGroups = true;
897
- device.pushErrorScope('validation');
898
- let pipelineScopeOpen = true;
899
- try {
900
- const pipelineConstants = !this._bakePipelineConstants
901
- ? pipelineConstantsKeys.reduce((obj, key) => {
902
- obj[key] = +this._constants[key];
903
- return obj;
904
- }, {})
905
- : undefined;
906
- const diagnostic = {
907
- label: 'Ray Trace',
908
- entryPoint: 'main',
909
- firstFrameRequired: true,
910
- status: 'compiling'
911
- };
912
- pipelineCompilationStartedAt = shaderCompilationNow();
913
- diagnostic.startedAtMs = 0;
914
- diagnostic.concurrentCompilationCount = 1;
915
- this._shaderCompilationDiagnostics = {
916
- status: 'compiling',
917
- sourceBytes,
918
- sourceLines,
919
- pipelineCount: 1,
920
- completedPipelineCount: 0,
921
- sourceGenerationTimeMs,
922
- moduleCreationTimeMs,
923
- modules: [
924
- {
925
- label: 'Ray Trace',
926
- sourceBytes,
927
- sourceLines,
928
- sourceFingerprint
929
- }
930
- ],
931
- pipelines: [{ ...diagnostic }],
932
- maximumConcurrentPipelineCount: 1,
933
- peakConcurrentPipelineCount: 1
934
- };
935
- let rayTracePipeline;
936
- try {
937
- rayTracePipeline = await this._shaderCache.createComputePipelineAsync({
938
- label: 'Ray Trace',
939
- layout: this._rayTracePipelineLayout,
940
- compute: {
941
- module: this._computeShaderModule,
942
- entryPoint: 'main',
943
- constants: pipelineConstants
944
- }
945
- });
946
- diagnostic.status = 'ready';
947
- }
948
- catch (error) {
949
- diagnostic.status = 'failed';
950
- diagnostic.error = getWebGPUErrorMessage(error);
951
- // Pipeline creation already reports WGSL failures. Pull the much
952
- // heavier module diagnostics only on that failure path so normal
953
- // startup does not schedule a second full-module inspection.
954
- const info = await this._computeShaderModule
955
- .getCompilationInfo()
956
- .catch(() => undefined);
957
- const shaderErrors = (info?.messages ?? [])
958
- .filter((message) => message.type === 'error');
959
- if (shaderErrors.length) {
960
- throw new Error(shaderErrors
961
- .map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
962
- .join('\n'));
963
- }
964
- throw error;
965
- }
966
- finally {
967
- diagnostic.endedAtMs =
968
- shaderCompilationNow() - pipelineCompilationStartedAt;
969
- diagnostic.durationMs = diagnostic.endedAtMs;
970
- }
971
- this._rayTracePipeline = rayTracePipeline;
972
- this._depthCompute = { module: this._computeShaderModule, constants: pipelineConstants };
973
- const pipelineError = await device.popErrorScope();
974
- pipelineScopeOpen = false;
975
- if (pipelineError)
976
- throw pipelineError;
977
- const compilationCompletedAt = shaderCompilationNow();
978
- this._shaderCompilationDiagnostics = {
979
- ...this._shaderCompilationDiagnostics,
980
- status: 'ready',
981
- completedPipelineCount: 1,
982
- pipelines: [{ ...diagnostic }],
983
- pipelineCompilationTimeMs: compilationCompletedAt - pipelineCompilationStartedAt,
984
- totalTimeMs: compilationCompletedAt - compilationStartedAt
985
- };
986
- }
987
- catch (error) {
988
- const scopedError = pipelineScopeOpen
989
- ? await device.popErrorScope().catch(() => null)
990
- : null;
991
- const detail = joinWebGPUErrors(error, scopedError);
992
- if (!this._bakePipelineConstants &&
993
- isWebGPUPipelineSpecializationFailure(error, scopedError)) {
994
- const failedAt = shaderCompilationNow();
995
- if (this._shaderCompilationDiagnostics?.status === 'compiling') {
996
- this._shaderCompilationDiagnostics = {
997
- ...this._shaderCompilationDiagnostics,
998
- status: 'failed',
999
- pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
1000
- ? undefined
1001
- : failedAt - pipelineCompilationStartedAt,
1002
- totalTimeMs: compilationStartedAt == null
1003
- ? undefined
1004
- : failedAt - compilationStartedAt,
1005
- error: detail
1006
- };
1007
- }
1008
- // WebKit can accept a large WGSL module but fail while translating
1009
- // its specialization-constant variant to Metal. Baking the values
1010
- // into WGSL lets its front end eliminate disabled feature branches
1011
- // before creating the Metal library.
1012
- this._bakePipelineConstants = true;
1013
- this._rayTracePipeline = undefined;
1014
- this._rayTraceBindGroups = undefined;
1015
- this._computeShaderModule = undefined;
1016
- this._prevPipelineConstants = {};
1017
- console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
1018
- return;
1019
- }
1020
- throw new Error(detail);
823
+ Object.assign(variant.diagnostics, {
824
+ sourceBytes: source.bytes, sourceLines: source.lines,
825
+ modules: [{ label: 'Ray Trace', sourceBytes: source.bytes, sourceLines: source.lines, sourceFingerprint: source.fingerprint }]
826
+ });
827
+ const { module } = source;
828
+ const layout = this._createPipelineLayout(!!bindless, photonMapping);
829
+ const pipelineConstants = !bake
830
+ ? pipelineConstantsKeys.reduce((obj, key) => {
831
+ obj[key] = +constants[key];
832
+ return obj;
833
+ }, {})
834
+ : undefined;
835
+ const lightSource = lightEmitter && emitterSource('lightTracing');
836
+ const photonSource = photonEmitter && emitterSource('photonMapping');
837
+ // Scopes capture all device errors while open, so one must never span an
838
+ // await: frames presented meanwhile would be blamed on this pipeline.
839
+ device.pushErrorScope('validation');
840
+ const request = this._shaderCache.createComputePipelineAsync({
841
+ label: 'Ray Trace', layout,
842
+ compute: { module, entryPoint: 'main', constants: pipelineConstants }
843
+ });
844
+ const scope = device.popErrorScope().catch(() => null);
845
+ let pipeline;
846
+ try {
847
+ pipeline = await request;
848
+ const scopedError = await scope;
849
+ if (scopedError)
850
+ throw scopedError;
851
+ }
852
+ catch (error) {
853
+ const scopedError = await scope;
854
+ // Pipeline creation already reports WGSL failures. Pull the much
855
+ // heavier module diagnostics only on that failure path so normal
856
+ // startup does not schedule a second full-module inspection.
857
+ const info = await module.getCompilationInfo().catch(() => undefined);
858
+ const shaderErrors = (info?.messages ?? []).filter((message) => message.type === 'error');
859
+ if (shaderErrors.length) {
860
+ throw new Error(shaderErrors
861
+ .map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
862
+ .join('\n'));
1021
863
  }
864
+ if (isWebGPUPipelineSpecializationFailure(error, scopedError))
865
+ throw error;
866
+ throw new Error(joinWebGPUErrors(error, scopedError));
1022
867
  }
1023
- // TODO rebind when lights, materials, bvh changed.
1024
- if (this._needsUpdateBindGroups) {
1025
- this._needsUpdateBindGroups = false;
1026
- this._updateBindGroups();
868
+ const groups = lightEmitter || photonEmitter ? groupIndices.map((i) => pipeline.getBindGroupLayout(i)) : [];
869
+ const [light, photon] = await Promise.all([
870
+ lightEmitter?.prepare(lightSource, groups, !!bindless),
871
+ photonEmitter?.prepare(photonSource, groups, !!bindless)
872
+ ]);
873
+ Object.assign(variant, { pipeline, lightEmitter: light, photonEmitter: photon,
874
+ depthCompute: { module, constants: pipelineConstants } });
875
+ };
876
+ const finishDiagnostics = (status, error) => {
877
+ const totalTimeMs = shaderCompilationNow() - startedAt;
878
+ Object.assign(variant.diagnostics, {
879
+ status, completedPipelineCount: status === 'ready' ? 1 : 0, error,
880
+ pipelines: [{ ...pipelineDiagnostic, status, error, endedAtMs: totalTimeMs, durationMs: totalTimeMs }],
881
+ pipelineCompilationTimeMs: totalTimeMs, totalTimeMs
882
+ });
883
+ };
884
+ const run = () => compile().then(() => {
885
+ variant.status = 'ready';
886
+ finishDiagnostics('ready');
887
+ }, (error) => {
888
+ finishDiagnostics('failed', getWebGPUErrorMessage(error));
889
+ if (this._disposed)
890
+ return;
891
+ if (!bake && isWebGPUPipelineSpecializationFailure(error)) {
892
+ // WebKit can accept a large WGSL module but fail while translating
893
+ // its specialization-constant variant to Metal. Baking the values
894
+ // into WGSL lets its front end eliminate disabled feature branches
895
+ // before creating the Metal library. The next request has a new
896
+ // signature, so this one stays pending until it is replaced.
897
+ this._bakePipelineConstants = true;
898
+ this._traceVariants.delete(signature);
899
+ console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
900
+ return;
1027
901
  }
1028
- this._failedPipelineSignature = undefined;
1029
- }
1030
- catch (error) {
1031
- this._pipelineError = error;
1032
- const failedAt = shaderCompilationNow();
1033
- if (this._shaderCompilationDiagnostics?.status === 'compiling') {
1034
- this._shaderCompilationDiagnostics = {
1035
- ...this._shaderCompilationDiagnostics,
1036
- status: 'failed',
1037
- pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
1038
- ? undefined
1039
- : failedAt - pipelineCompilationStartedAt,
1040
- totalTimeMs: compilationStartedAt == null
1041
- ? undefined
1042
- : failedAt - compilationStartedAt,
1043
- error: getWebGPUErrorMessage(error)
1044
- };
902
+ // Nobody needs an obsolete result, and a later request should try again.
903
+ if (this._requestedVariant !== variant) {
904
+ this._traceVariants.delete(signature);
905
+ variant.status = 'failed';
906
+ return;
1045
907
  }
1046
- this._failedPipelineSignature = attemptedPipelineSignature;
1047
- // Do not allow the render loop to submit the previous/partial state.
1048
- // The failed signature prevents retrying the same shader every frame;
1049
- // changing a pipeline-affecting setting permits a new attempt.
1050
- this._rayTracePipeline = undefined;
1051
- this._rayTraceBindGroups = undefined;
1052
- this._prevPipelineConstants = {};
1053
- this._needsUpdateBindGroups = true;
908
+ variant.status = 'failed';
909
+ variant.error = error;
1054
910
  console.error('Failed to update WebGPU ray tracing pipeline.', error);
911
+ });
912
+ variant.settled = new Promise((resolve) => {
913
+ this._queuedCompiles.push({ variant, signature, run, release: resolve });
914
+ });
915
+ this._traceVariants.set(signature, variant);
916
+ if (this._queuedCompiles.length > MAX_QUEUED_COMPILES) {
917
+ // A dropped variant stays pending, because a render that still wants it
918
+ // replaces it with a new request on its next frame.
919
+ const obsolete = this._queuedCompiles.shift();
920
+ if (this._traceVariants.get(obsolete.signature) === obsolete.variant)
921
+ this._traceVariants.delete(obsolete.signature);
922
+ obsolete.release();
923
+ }
924
+ for (const [key, retained] of this._traceVariants) {
925
+ if (this._traceVariants.size <= MAX_TRACE_VARIANTS)
926
+ break;
927
+ if (retained.status !== 'pending' && retained !== this._activeVariant)
928
+ this._traceVariants.delete(key);
929
+ }
930
+ this._pumpCompiles();
931
+ return variant;
932
+ }
933
+ /** Runs the most recently requested variants first: a dragged slider's last value is the one in use. */
934
+ _pumpCompiles() {
935
+ while (this._activeCompiles < MAX_CONCURRENT_COMPILES && this._queuedCompiles.length) {
936
+ const job = this._queuedCompiles.pop();
937
+ this._activeCompiles++;
938
+ void job.run().finally(() => {
939
+ this._activeCompiles--;
940
+ job.release();
941
+ if (!this._disposed)
942
+ this._pumpCompiles();
943
+ });
1055
944
  }
1056
- finally {
1057
- this._isCompilingShader = false;
945
+ }
946
+ _requestQueuedCompile(variant) {
947
+ const index = this._queuedCompiles.findIndex((job) => job.variant === variant);
948
+ if (index >= 0)
949
+ this._queuedCompiles.push(...this._queuedCompiles.splice(index, 1));
950
+ }
951
+ _prepareAovPipelines(kinds) {
952
+ const trace = this._rayTracePipeline;
953
+ if (!trace || !this._depthCompute)
954
+ return;
955
+ const reflectance = (aovMask(kinds) & 24) !== 0;
956
+ let variants = this._aovPipelines.get(trace);
957
+ if (!variants) {
958
+ variants = new Map();
959
+ this._aovPipelines.set(trace, variants);
960
+ }
961
+ let pipelines = variants.get(reflectance);
962
+ if (!pipelines) {
963
+ pipelines = {};
964
+ variants.set(reflectance, pipelines);
965
+ const pending = pipelines;
966
+ const textures = this._bindless, groupIndex = textures ? 2 : 3;
967
+ const output = this._device.createBindGroupLayout({ entries: [
968
+ { binding: 5, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
969
+ { binding: 6, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
970
+ { binding: 7, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
971
+ { binding: 8, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
972
+ { binding: 9, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } }
973
+ ] });
974
+ const descriptor = { bindGroupLayouts: Array.from({ length: groupIndex }, (_, i) => trace.getBindGroupLayout(i)).concat(output) };
975
+ const layout = this._device.createPipelineLayout(textures ? resourceTableLayout(descriptor) : descriptor);
976
+ const compute = { ...this._depthCompute, constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1, AOV_REFLECTANCE: reflectance ? 1 : 0 } };
977
+ // Keep the UI responsive during first use, and reuse both variants across
978
+ // preview/final target changes rather than recreating synchronous pipelines.
979
+ void Promise.all([
980
+ this._shaderCache.createComputePipelineAsync({ label: 'AOV accumulation', layout, compute: { ...compute, entryPoint: 'aovTrace' } }),
981
+ this._shaderCache.createComputePipelineAsync({ label: 'AOV center', layout, compute: { ...compute, entryPoint: 'aovTraceCenter' } })
982
+ ]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; this._aovPipelinesFailed = true; });
983
+ }
984
+ if (pipelines.error)
985
+ throw pipelines.error;
986
+ return pipelines;
987
+ }
988
+ prepareAovs(kinds) {
989
+ const pipelines = this._prepareAovPipelines(kinds);
990
+ return !!pipelines?.accumulation && !!pipelines.center;
991
+ }
992
+ renderAovs(state, camera, samples, cap, kinds) {
993
+ const groups = this._rayTraceBindGroups;
994
+ if (!groups || !this._tlas || !this._preparedMeshData || !kinds.length)
995
+ return false;
996
+ const pipelines = this._prepareAovPipelines(kinds);
997
+ if (!pipelines?.accumulation || !pipelines.center)
998
+ return false;
999
+ state.resize(this._width, this._height);
1000
+ const textures = this._bindless, groupIndex = textures ? 2 : 3;
1001
+ const target = Math.min(samples, cap);
1002
+ if (state.centerReady && state.samples >= target)
1003
+ return true;
1004
+ if (!state.frame) {
1005
+ const primitiveIds = this._aovPrimitiveCache.get(this._preparedMeshData, this._tlas);
1006
+ const packed = packAovScene(this._preparedMeshData, this._tlas, camera, this._width, this._height, state.history, primitiveIds, false);
1007
+ state.frame = packed.frame;
1008
+ state.setScene(packed.words, primitiveIds);
1009
+ }
1010
+ this._device.queue.writeBuffer(this._uniforms.rtUniform, 0, this._rtUniform.buffer);
1011
+ this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
1012
+ const device = this._device;
1013
+ const run = (pipeline, sample) => {
1014
+ // A fresh uniform for each queued dispatch keeps batch samples independent.
1015
+ const parameters = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM, mappedAtCreation: true });
1016
+ new Uint32Array(parameters.getMappedRange()).set([state.width, state.height, sample, aovMask(kinds)]);
1017
+ parameters.unmap();
1018
+ const output = device.createBindGroup({ layout: pipeline.getBindGroupLayout(groupIndex), entries: [
1019
+ { binding: 5, resource: { buffer: state.accumulation } }, { binding: 6, resource: { buffer: state.center } },
1020
+ { binding: 7, resource: { buffer: state.scene } }, { binding: 8, resource: { buffer: parameters } }, { binding: 9, resource: { buffer: state.previousCenter } }
1021
+ ] });
1022
+ const encoder = device.createCommandEncoder({ label: 'AOV' }), pass = encoder.beginComputePass();
1023
+ if (textures?.table)
1024
+ setResourceTable(pass, textures.table);
1025
+ pass.setPipeline(pipeline);
1026
+ for (let i = 0; i < groupIndex; i++)
1027
+ pass.setBindGroup(i, groups[i]);
1028
+ pass.setBindGroup(groupIndex, output);
1029
+ pass.dispatchWorkgroups(Math.ceil(state.width / 8), Math.ceil(state.height / 8));
1030
+ pass.end();
1031
+ device.queue.submit([encoder.finish()]);
1032
+ parameters.destroy();
1033
+ };
1034
+ if (!state.centerReady) {
1035
+ run(pipelines.center, 0);
1036
+ state.centerReady = true;
1037
+ state.history.accept(state.frame);
1038
+ }
1039
+ if ((aovMask(kinds) & 31) === 0)
1040
+ state.samples = target;
1041
+ while (state.samples < target) {
1042
+ run(pipelines.accumulation, state.samples);
1043
+ state.samples++;
1058
1044
  }
1045
+ return true;
1059
1046
  }
1060
1047
  async readDepthSamples(area, aspect) {
1061
1048
  const tracePipeline = this._rayTracePipeline;
1062
1049
  const groups = this._rayTraceBindGroups;
1063
- if (!tracePipeline || !groups || !this._depthCompute || this._isCompilingShader)
1050
+ const textures = this._bindless, textureRevision = textures?.revision;
1051
+ if (!tracePipeline || !groups || !this._depthCompute || this.isCompilingShader())
1064
1052
  return;
1065
1053
  const packed = packDepthSampleArea(area, aspect ?? this._width / this._height);
1066
1054
  if (this._depthTracePipeline !== tracePipeline || !this._depthPipeline) {
@@ -1071,10 +1059,11 @@ ${rayTraceCompute({
1071
1059
  { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } }
1072
1060
  ]
1073
1061
  });
1074
- const layout = this._shaderCache.createPipelineLayout({
1062
+ const descriptor = {
1075
1063
  label: 'Depth query layout',
1076
- bindGroupLayouts: [0, 1, 2].map(i => tracePipeline.getBindGroupLayout(i)).concat(outputLayout)
1077
- });
1064
+ bindGroupLayouts: (textures ? [0, 1] : [0, 1, 2]).map(i => tracePipeline.getBindGroupLayout(i)).concat(outputLayout)
1065
+ };
1066
+ const layout = this._shaderCache.createPipelineLayout(textures ? resourceTableLayout(descriptor) : descriptor);
1078
1067
  this._depthTracePipeline = tracePipeline;
1079
1068
  this._depthPipeline = this._shaderCache.createComputePipelineAsync({
1080
1069
  label: 'Depth query', layout,
@@ -1084,7 +1073,7 @@ ${rayTraceCompute({
1084
1073
  }
1085
1074
  const pipeline = await this._depthPipeline;
1086
1075
  // Scene uploads can replace the buffers while the small query compiles.
1087
- if (tracePipeline !== this._rayTracePipeline || groups !== this._rayTraceBindGroups)
1076
+ if (tracePipeline !== this._rayTracePipeline || groups !== this._rayTraceBindGroups || textures !== this._bindless || textureRevision !== textures?.revision)
1088
1077
  return;
1089
1078
  const device = this._device;
1090
1079
  const bytes = DEPTH_SAMPLE_COUNT * 16;
@@ -1096,16 +1085,18 @@ ${rayTraceCompute({
1096
1085
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
1097
1086
  try {
1098
1087
  device.queue.writeBuffer(params, 0, packed);
1099
- const outputGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(3), entries: [
1088
+ const outputGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(textures ? 2 : 3), entries: [
1100
1089
  { binding: 0, resource: { buffer: output } },
1101
1090
  { binding: 4, resource: { buffer: params } }
1102
1091
  ] });
1103
1092
  const encoder = device.createCommandEncoder({ label: 'Depth query' });
1104
1093
  const pass = encoder.beginComputePass();
1094
+ if (textures?.table)
1095
+ setResourceTable(pass, textures.table);
1105
1096
  pass.setPipeline(pipeline);
1106
- for (let i = 0; i < 3; i++)
1097
+ for (let i = 0; i < (textures ? 2 : 3); i++)
1107
1098
  pass.setBindGroup(i, groups[i]);
1108
- pass.setBindGroup(3, outputGroup);
1099
+ pass.setBindGroup(textures ? 2 : 3, outputGroup);
1109
1100
  pass.dispatchWorkgroups(1);
1110
1101
  pass.end();
1111
1102
  encoder.copyBufferToBuffer(output, 0, readback, 0, bytes);
@@ -1121,15 +1112,17 @@ ${rayTraceCompute({
1121
1112
  params.destroy();
1122
1113
  }
1123
1114
  }
1115
+ /** Whether the latest request waits on GPU work that will settle on its own. */
1124
1116
  isCompilingShader() {
1125
- return this._isCompilingShader;
1117
+ return !!this._awaitingRetirement || this._requestedVariant?.status === 'pending';
1126
1118
  }
1127
1119
  isPipelineReady() {
1128
1120
  return ((!!this._rayTracePipeline) &&
1129
1121
  !!this._rayTraceBindGroups);
1130
1122
  }
1123
+ /** Failure of the latest request. A failed variant does not affect other renders. */
1131
1124
  getPipelineError() {
1132
- return this._pipelineError;
1125
+ return this._resourceError ?? this._requestedVariant?.error;
1133
1126
  }
1134
1127
  _updateBindGroups() {
1135
1128
  const { _device } = this;
@@ -1140,50 +1133,66 @@ ${rayTraceCompute({
1140
1133
  if (!this._bvhBuffer || !this._instancesBuffer) {
1141
1134
  throw new Error('WebGPU BVH buffers are not ready.');
1142
1135
  }
1143
- const bufferBindGroup = _device.createBindGroup({
1144
- layout: bindGroupPipeline.getBindGroupLayout(0),
1145
- entries: [
1146
- this._bvhBuffer,
1147
- this._instancesBuffer,
1148
- this._attrBuffers,
1149
- this._materialsBuffer
1150
- ].map((buffer, i) => ({
1151
- binding: i,
1152
- resource: {
1153
- buffer: buffer
1154
- }
1155
- }))
1156
- });
1157
- const texturesBindGroup = _device.createBindGroup({
1158
- layout: bindGroupPipeline.getBindGroupLayout(1),
1159
- entries: [
1160
- {
1161
- binding: 0,
1162
- resource: this._mapsSampler
1163
- },
1164
- {
1165
- binding: 1,
1166
- resource: this._maps.createView()
1167
- },
1168
- {
1169
- binding: 3,
1170
- resource: {
1171
- buffer: this._mapInfoUniform
1136
+ const bufferEntries = [
1137
+ this._bvhBuffer,
1138
+ this._instancesBuffer,
1139
+ this._attrBuffers,
1140
+ this._materialsBuffer
1141
+ ].map((buffer, i) => ({
1142
+ binding: i,
1143
+ resource: {
1144
+ buffer: buffer
1145
+ }
1146
+ }));
1147
+ const textureEntries = [
1148
+ ...(this._bindless
1149
+ ? []
1150
+ : [
1151
+ {
1152
+ binding: 0,
1153
+ resource: this._mapsSampler
1154
+ },
1155
+ {
1156
+ binding: 1,
1157
+ resource: this._maps.createView()
1172
1158
  }
1173
- },
1174
- { binding: 4, resource: this._envTex.createView() },
1175
- { binding: 5, resource: this._envDistributionTex.createView() },
1176
- { binding: 6, resource: this._backgroundTex.createView() },
1177
- { binding: 8, resource: this._lightSamplingTexture.createView() },
1178
- { binding: 9, resource: this._openPbrEnergyLut.createView() },
1179
- {
1180
- binding: 10,
1181
- resource: this._adaptiveSamplingTexture.createView()
1159
+ ]),
1160
+ {
1161
+ binding: 3,
1162
+ resource: {
1163
+ buffer: this._bindless?.metadata ?? this._mapInfoUniform
1182
1164
  }
1183
- ]
1165
+ },
1166
+ { binding: 4, resource: this._envTex.createView() },
1167
+ { binding: 5, resource: this._envDistributionTex.createView() },
1168
+ { binding: 6, resource: this._backgroundTex.createView() },
1169
+ ...(this._photonMapping
1170
+ ? [
1171
+ { binding: 11, resource: this._photonMap.get(this._activeTarget).data.createView() },
1172
+ { binding: 12, resource: this._photonMap.get(this._activeTarget).headers.createView() }
1173
+ ]
1174
+ : []),
1175
+ { binding: 8, resource: this._lightSamplingTexture.createView() },
1176
+ { binding: 9, resource: this._openPbrEnergyLut.createView() },
1177
+ {
1178
+ binding: 10,
1179
+ resource: this._adaptiveSamplingTexture.createView()
1180
+ }
1181
+ ];
1182
+ const bufferBindGroup = _device.createBindGroup({
1183
+ layout: bindGroupPipeline.getBindGroupLayout(0),
1184
+ entries: this._bindless
1185
+ ? bufferEntries.concat(textureEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
1186
+ : bufferEntries
1184
1187
  });
1188
+ const texturesBindGroup = this._bindless
1189
+ ? undefined
1190
+ : _device.createBindGroup({
1191
+ layout: bindGroupPipeline.getBindGroupLayout(1),
1192
+ entries: textureEntries
1193
+ });
1185
1194
  const uniformBindGroup = _device.createBindGroup({
1186
- layout: bindGroupPipeline.getBindGroupLayout(2),
1195
+ layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 1 : 2),
1187
1196
  entries: Object.keys(this._uniforms).map((key, i) => ({
1188
1197
  binding: i,
1189
1198
  resource: {
@@ -1191,24 +1200,41 @@ ${rayTraceCompute({
1191
1200
  }
1192
1201
  }))
1193
1202
  });
1203
+ if (this._photonMapping) {
1204
+ // Explicit layouts still track unused textures. Bind a placeholder while
1205
+ // emission writes the real packed map through its storage view.
1206
+ const emissionEntries = textureEntries.map((entry) => entry.binding === 11 || entry.binding === 12
1207
+ ? { ...entry, resource: this._photonMap.placeholder.createView() }
1208
+ : entry);
1209
+ const sceneGroup = _device.createBindGroup({
1210
+ layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 0 : 1),
1211
+ entries: this._bindless
1212
+ ? bufferEntries.concat(emissionEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
1213
+ : emissionEntries
1214
+ });
1215
+ this._photonEmissionGroups = this._bindless
1216
+ ? [sceneGroup, uniformBindGroup]
1217
+ : [bufferBindGroup, sceneGroup, uniformBindGroup];
1218
+ }
1194
1219
  const target = this._targetOutputs.get(this._activeTarget);
1195
- const outputBindGroup = target.pipeline === bindGroupPipeline && target.bindGroup || _device.createBindGroup({
1196
- layout: bindGroupPipeline.getBindGroupLayout(3),
1197
- entries: [
1198
- this._outColorBuffer,
1199
- this._outNormalBuffer,
1200
- this._outAlbedoBuffer,
1201
- this._outShadowCatcherBuffer
1202
- ].map((buffer, i) => ({
1203
- binding: i,
1204
- resource: { buffer }
1205
- }))
1206
- });
1220
+ const outputBindGroup = (target.pipeline === bindGroupPipeline && target.bindGroup) ||
1221
+ _device.createBindGroup({
1222
+ layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 2 : 3),
1223
+ entries: [
1224
+ this._outColorBuffer,
1225
+ this._outNormalBuffer,
1226
+ this._outAlbedoBuffer,
1227
+ this._outShadowCatcherBuffer
1228
+ ].map((buffer, i) => ({
1229
+ binding: i,
1230
+ resource: { buffer }
1231
+ }))
1232
+ });
1207
1233
  target.bindGroup = outputBindGroup;
1208
1234
  target.pipeline = bindGroupPipeline;
1209
1235
  this._rayTraceBindGroups = [
1210
1236
  bufferBindGroup,
1211
- texturesBindGroup,
1237
+ ...(texturesBindGroup ? [texturesBindGroup] : []),
1212
1238
  uniformBindGroup,
1213
1239
  outputBindGroup
1214
1240
  ];
@@ -1230,6 +1256,36 @@ ${rayTraceCompute({
1230
1256
  encoder.clearBuffer(this._outAlbedoBuffer);
1231
1257
  encoder.clearBuffer(this._outShadowCatcherBuffer);
1232
1258
  }
1259
+ const offset = this._rtUniform.views.scaleOffset;
1260
+ const causticBatch = this._lightTracing && offset[2] === tiles[0] - 1 && offset[3] === tiles[1] - 1;
1261
+ const photonBatch = this._photonMapping && offset[2] === 0 && offset[3] === 0;
1262
+ if (photonBatch && this._photonMap) {
1263
+ const requested = this._causticSettings?.photonMapping, initial = typeof requested === 'object' ? requested.initialRadius : undefined;
1264
+ const radius = Number.isFinite(initial) && initial > 0 ? initial : Math.max((causticDiagnostics.photonPerCasterRadius
1265
+ ? causticCasterSpheres(this._meshes).reduce((maximum, s) => Math.max(maximum, s[3]), 0)
1266
+ : this._causticBounds[3]) * 0.03, 1e-4);
1267
+ const batch = Math.max(1, this._rtUniform.views.sampleCount[0] - this._rtUniform.views.sampleSequenceOffset[0] + 1);
1268
+ const photonViewProjection = mat4.create();
1269
+ mat4.invert(photonViewProjection, this._viewProjInv);
1270
+ this._photonMap.encode(encoder, this._photonEmissionGroups, this._activeTarget, photonRadius(radius, causticDiagnostics.photonDebugBatch ?? batch, causticDiagnostics.photonAlpha ?? 2 / 3,
1271
+ // Allocation limits may reduce the requested count.
1272
+ this._photonMap.get(this._activeTarget).count, causticDiagnostics.photonRadiusScaling), {
1273
+ querySet: timing?.querySet,
1274
+ resourceTable: this._bindless?.table,
1275
+ orthographic: !this._causticPerspective,
1276
+ progressiveBatch: batch,
1277
+ width: this._width,
1278
+ height: this._height,
1279
+ clear,
1280
+ run: true,
1281
+ photons: 1,
1282
+ batch: Math.floor(this._rtUniform.views.sampleCount[0]),
1283
+ ...this._transport?.uniform,
1284
+ viewProjection: photonViewProjection,
1285
+ casterSphere: this._causticBounds,
1286
+ cameraScale: this._causticCameraScale
1287
+ });
1288
+ }
1233
1289
  const descriptor = {};
1234
1290
  const querySet = timing?.querySet;
1235
1291
  if (querySet) {
@@ -1240,6 +1296,8 @@ ${rayTraceCompute({
1240
1296
  };
1241
1297
  }
1242
1298
  const pass = encoder.beginComputePass(descriptor);
1299
+ if (this._bindless?.table)
1300
+ setResourceTable(pass, this._bindless.table);
1243
1301
  if (!this._rayTracePipeline || !this._rayTraceBindGroups) {
1244
1302
  throw new Error('WebGPU ray tracing pass was submitted before it was ready.');
1245
1303
  }
@@ -1247,8 +1305,32 @@ ${rayTraceCompute({
1247
1305
  this._rayTraceBindGroups.forEach((bindGroup, idx) => {
1248
1306
  pass.setBindGroup(idx, bindGroup);
1249
1307
  });
1250
- pass.dispatchWorkgroups(Math.ceil(dispatchWidth / WORKGROUP_SIZE), Math.ceil(dispatchHeight / WORKGROUP_SIZE), 1);
1308
+ researchTransport()?.capture(this);
1309
+ pass.dispatchWorkgroups(Math.ceil(dispatchWidth / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), Math.ceil(dispatchHeight / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), 1);
1251
1310
  pass.end();
1311
+ if (this._lightTracing && this._causticLight) {
1312
+ const viewProjection = mat4.create();
1313
+ mat4.invert(viewProjection, this._viewProjInv);
1314
+ const requested = this._causticSettings?.lightTracing;
1315
+ const count = typeof requested === 'object' ? requested.photonsPerSample : undefined;
1316
+ const photons = Math.min(this._device.limits.maxComputeWorkgroupsPerDimension * 64, Math.max(1, Math.round(Number.isFinite(count) ? count : (this._width * this._height) / 4)));
1317
+ this._causticLight.encode(encoder, this._rayTraceBindGroups.slice(0, this._bindless ? 2 : 3), this._activeTarget, {
1318
+ querySet: causticBatch ? querySet : undefined,
1319
+ width: this._width,
1320
+ height: this._height,
1321
+ clear,
1322
+ run: !!causticBatch,
1323
+ photons,
1324
+ batch: Math.floor(this._rtUniform.views.sampleCount[0]),
1325
+ ...this._transport?.uniform,
1326
+ resourceTable: this._bindless?.table,
1327
+ orthographic: !this._causticPerspective,
1328
+ viewProjection,
1329
+ casterSphere: this._causticBounds,
1330
+ cameraScale: this._causticCameraScale
1331
+ });
1332
+ }
1333
+ return (causticBatch ? 1 : 0) | (photonBatch ? 2 : 0);
1252
1334
  }
1253
1335
  async getDebugBVHData() {
1254
1336
  if (!this._debugBVHBuffer) {
@@ -1259,9 +1341,19 @@ ${rayTraceCompute({
1259
1341
  };
1260
1342
  }
1261
1343
  getDiagnostics() {
1344
+ const atlas = this._materialData instanceof MaterialTable ? this._materialData.textureAtlasStats : undefined;
1262
1345
  return {
1263
- textureAtlas: this._materialData?.textureAtlasStats,
1264
- shaderCompilation: this._shaderCompilationDiagnostics,
1346
+ textureAtlas: atlas,
1347
+ materialTextures: {
1348
+ requested: this._textureSelection.requested,
1349
+ active: this._bindless ? 'bindless' : 'atlas',
1350
+ fallbackReason: this._textureSelection.fallbackReason,
1351
+ ...(this._bindless?.diagnostics ?? {
1352
+ textureCount: atlas?.textureCount ?? 0, estimatedBytes: atlas?.estimatedBytes ?? 0,
1353
+ scale: atlas?.scale ?? 1, metadataBytes: 0, resourceTableCapacity: 0, retiredBytes: 0
1354
+ })
1355
+ },
1356
+ shaderCompilation: this._requestedVariant?.diagnostics,
1265
1357
  lightSampling: {
1266
1358
  emissiveTriangles: this._emissiveTriangles.length,
1267
1359
  activeChoices: this._rtUniform.views.lightChoiceCount[0],
@@ -1281,15 +1373,6 @@ ${rayTraceCompute({
1281
1373
  if (next === this._alphaBlendStrategy)
1282
1374
  return false;
1283
1375
  this._alphaBlendStrategy = next;
1284
- if (this._constants.HAS_ALPHA_BLEND) {
1285
- this._computeShaderModule = undefined;
1286
- this._rayTracePipeline = undefined;
1287
- this._rayTracePipelineLayout = undefined;
1288
- this._rayTraceBindGroups = undefined;
1289
- this._prevPipelineConstants = {};
1290
- this._failedPipelineSignature = undefined;
1291
- this._needsUpdateBindGroups = true;
1292
- }
1293
1376
  return true;
1294
1377
  }
1295
1378
  hasDepthOfField() {
@@ -1325,15 +1408,14 @@ ${rayTraceCompute({
1325
1408
  return changed;
1326
1409
  }
1327
1410
  setSamplingPattern(pattern, maxSamples, sequenceSeed = 0) {
1328
- const code = samplingPatternCode(pattern);
1329
- const logSPP = zSobolLog2SPP(code === 1 ? maxSamples - 1 : maxSamples);
1411
+ const sampling = traceSampling(pattern, maxSamples, sequenceSeed);
1412
+ const { code, logSPP } = sampling;
1330
1413
  const views = this._rtUniform.views;
1331
1414
  const changed = this._constants.SAMPLING_PATTERN !== code ||
1332
1415
  views.sequenceSeed[0] !== (sequenceSeed >>> 0) ||
1333
1416
  (code === 1 && views.zSobolLogSPP[0] !== logSPP);
1334
1417
  this._constants.SAMPLING_PATTERN = code;
1335
- views.sequenceSeed[0] = sequenceSeed;
1336
- views.zSobolLogSPP[0] = logSPP;
1418
+ writeTraceSampling(views, sampling);
1337
1419
  return changed;
1338
1420
  }
1339
1421
  setSampleSequenceOffset(offset) {
@@ -1354,10 +1436,27 @@ ${rayTraceCompute({
1354
1436
  this._device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, data, { bytesPerRow: width * Float32Array.BYTES_PER_ELEMENT }, [width, height]);
1355
1437
  }
1356
1438
  setQuality(options) {
1357
- this._rtUniform.views.bounces[0] = normalizeBounces(options.bounces);
1358
- this._rtUniform.views.bevel[0] = +options.bevel;
1359
- this._rtUniform.views.dispersionWavelengths[0] = options.dispersionWavelengths;
1360
- this._rtUniform.views.directLightCandidates[0] = normalizeDirectLightCandidates(options.directLightCandidates);
1439
+ WGPURayTracePass.prototype._setCaustics.call(this, options.caustics);
1440
+ writeTraceQuality(this._rtUniform.views, options);
1441
+ }
1442
+ _setCaustics(settings) {
1443
+ this._causticSettings = settings;
1444
+ const transport = researchTransport()?.configure(this, 'webgpu');
1445
+ if (!!transport?.enabled !== !!this._transport?.enabled)
1446
+ this._needsUpdateLightSamplingData = true;
1447
+ this._transport = transport;
1448
+ const hasCaster = !!(settings?.lightTracing || settings?.photonMapping) &&
1449
+ this._meshes.some((mesh) => mesh.materials.some(isCausticSpecular));
1450
+ const photonSupported = !(settings?.lightTracing || settings?.photonMapping) ||
1451
+ !hasCaster ||
1452
+ !hasStochasticCausticCaster(this._meshes);
1453
+ this._lightTracing = !!settings?.lightTracing && hasCaster && photonSupported;
1454
+ const photonMapping = !!settings?.photonMapping && hasCaster && photonSupported;
1455
+ this._photonMapping = photonMapping;
1456
+ this._causticKey =
1457
+ this._transport?.enabled || this._lightTracing || this._photonMapping
1458
+ ? `${this._lightTracing}/${this._photonMapping}/${causticDiagnostics.fullEmissionRandom !== false}/${causticDiagnostics.photonMaxDepth ?? 20}/${causticDiagnostics.debugGroup}/${causticDiagnostics.photonDirectSubset !== false}/${this._transport?.key ?? ''}`
1459
+ : '';
1361
1460
  }
1362
1461
  _setEnvMap(envImage) {
1363
1462
  const device = this._device;
@@ -1365,53 +1464,74 @@ ${rayTraceCompute({
1365
1464
  if (!environmentMapSourcesMatch(envImage, this._envImageSource) ||
1366
1465
  !this._envTex) {
1367
1466
  this._envImageSource = envImage;
1368
- this._envTex?.destroy();
1369
- this._envDistributionTex?.destroy();
1370
- const distribution = envImage && envMapDistribution(envImage);
1371
- const usage = GPUTextureUsage.TEXTURE_BINDING |
1372
- GPUTextureUsage.COPY_DST |
1373
- GPUTextureUsage.RENDER_ATTACHMENT;
1374
- this._envTex = device.createTexture({
1375
- format: 'rgba32float',
1376
- usage,
1377
- label: 'Env',
1378
- size: {
1379
- width: envImage ? envImage.width : 1,
1380
- height: envImage ? envImage.height : 1,
1381
- depthOrArrayLayers: 1
1382
- }
1383
- });
1384
- this._envDistributionTex = device.createTexture({
1385
- format: 'rg32float',
1386
- usage,
1387
- label: 'Env Distribution',
1388
- size: {
1389
- width: distribution ? distribution.width : 1,
1390
- height: distribution ? distribution.height : 1,
1391
- depthOrArrayLayers: 1
1392
- }
1393
- });
1394
- if (envImage && distribution) {
1395
- device.queue.writeTexture({
1396
- texture: this._envTex
1397
- }, envImage.data.buffer, {
1398
- bytesPerRow: envImage.width * 4 * 4
1399
- }, {
1400
- width: envImage.width,
1401
- height: envImage.height,
1402
- depthOrArrayLayers: 1
1467
+ const sky = envImage && 'sky' in envImage ? envImage : undefined;
1468
+ if (sky && (sky.width + 2 > device.limits.maxTextureDimension2D || sky.height + 1 > device.limits.maxTextureDimension2D))
1469
+ throw new RangeError('ProceduralSky resolution exceeds the device texture size limit.');
1470
+ const gpuDistribution = envImage && (sky || envImage.width * envImage.height >= 65536) &&
1471
+ WGPUEnvMapDistribution.supported(device, envImage.width, envImage.height);
1472
+ if (sky && !gpuDistribution)
1473
+ throw new RangeError('ProceduralSky resolution exceeds the GPU sampling storage limit; reduce resolution.');
1474
+ const distribution = envImage && !('sky' in envImage) && !gpuDistribution ? envMapDistribution(envImage) : undefined;
1475
+ const distributionWidth = envImage ? envImage.width + 2 : 1;
1476
+ const distributionHeight = envImage ? envImage.height + 1 : 1;
1477
+ const width = envImage?.width ?? 1;
1478
+ const height = envImage?.height ?? 1;
1479
+ const resized = !this._envTex || this._envTex.width !== width || this._envTex.height !== height ||
1480
+ !this._envDistributionTex || this._envDistributionTex.width !== distributionWidth || this._envDistributionTex.height !== distributionHeight;
1481
+ if (resized) {
1482
+ this._envTex?.destroy();
1483
+ this._envDistributionTex?.destroy();
1484
+ const usage = GPUTextureUsage.TEXTURE_BINDING |
1485
+ GPUTextureUsage.COPY_DST |
1486
+ GPUTextureUsage.RENDER_ATTACHMENT;
1487
+ this._envTex = device.createTexture({
1488
+ format: 'rgba32float',
1489
+ usage: usage | GPUTextureUsage.STORAGE_BINDING,
1490
+ label: 'Env',
1491
+ size: {
1492
+ width,
1493
+ height,
1494
+ depthOrArrayLayers: 1
1495
+ }
1403
1496
  });
1404
- device.queue.writeTexture({
1405
- texture: this._envDistributionTex
1406
- }, distribution.data.buffer, {
1407
- bytesPerRow: distribution.width * 4 * 2
1408
- }, {
1409
- width: distribution.width,
1410
- height: distribution.height,
1411
- depthOrArrayLayers: 1
1497
+ this._envDistributionTex = device.createTexture({
1498
+ format: 'rg32float',
1499
+ usage: usage | GPUTextureUsage.STORAGE_BINDING,
1500
+ label: 'Env Distribution',
1501
+ size: {
1502
+ width: distributionWidth,
1503
+ height: distributionHeight,
1504
+ depthOrArrayLayers: 1
1505
+ }
1412
1506
  });
1413
1507
  }
1414
- this._needsUpdateBindGroups = true;
1508
+ if (envImage) {
1509
+ if ('sky' in envImage) {
1510
+ this._skyBaker ??= new WGPUSkyBaker(device);
1511
+ this._skyBaker.bake(envImage, this._envTex);
1512
+ }
1513
+ else
1514
+ device.queue.writeTexture({
1515
+ texture: this._envTex
1516
+ }, envImage.data.buffer, {
1517
+ bytesPerRow: envImage.width * 4 * 4
1518
+ }, {
1519
+ width: envImage.width,
1520
+ height: envImage.height,
1521
+ depthOrArrayLayers: 1
1522
+ });
1523
+ if (gpuDistribution) {
1524
+ this._envDistribution ??= new WGPUEnvMapDistribution(device);
1525
+ this._envDistribution.build(this._envTex, this._envDistributionTex);
1526
+ }
1527
+ else if (distribution) {
1528
+ this._envDistribution?.dispose();
1529
+ device.queue.writeTexture({ texture: this._envDistributionTex }, distribution.data.buffer, { bytesPerRow: distribution.width * 4 * 2 }, { width: distribution.width, height: distribution.height, depthOrArrayLayers: 1 });
1530
+ }
1531
+ }
1532
+ else
1533
+ this._envDistribution?.dispose();
1534
+ this._needsUpdateBindGroups ||= resized;
1415
1535
  }
1416
1536
  this._constants.USE_ENVMAP = !!envImage;
1417
1537
  }
@@ -1624,6 +1744,7 @@ ${rayTraceCompute({
1624
1744
  }
1625
1745
  setBackground(background) {
1626
1746
  this._rtUniform.views.backgroundMapTransform.set(backgroundMapTransform(background));
1747
+ this._rtUniform.views.backgroundMapRotation.set(backgroundMapRotation(background));
1627
1748
  this._constants.USE_BACKGROUND = !!background;
1628
1749
  const backgroundImage = background && !Array.isArray(background)
1629
1750
  ? generateBackgroundMapFromSceneBackground(background)
@@ -1658,74 +1779,51 @@ ${rayTraceCompute({
1658
1779
  return;
1659
1780
  }
1660
1781
  this._backgroundImageSource = backgroundImage;
1661
- this._backgroundTex?.destroy();
1662
1782
  const width = backgroundImage?.width ?? 1;
1663
1783
  const height = backgroundImage?.height ?? 1;
1664
- this._backgroundTex = this._device.createTexture({
1665
- format: 'rgba32float',
1666
- usage: GPUTextureUsage.TEXTURE_BINDING |
1667
- GPUTextureUsage.COPY_DST |
1668
- GPUTextureUsage.RENDER_ATTACHMENT,
1669
- label: 'Background',
1670
- size: { width, height, depthOrArrayLayers: 1 }
1671
- });
1672
- if (backgroundImage) {
1784
+ if (backgroundImage && 'sky' in backgroundImage && (width > this._device.limits.maxTextureDimension2D || height > this._device.limits.maxTextureDimension2D))
1785
+ throw new RangeError('ProceduralSky resolution exceeds the device texture size limit.');
1786
+ const resized = !this._backgroundTex || this._backgroundTex.width !== width || this._backgroundTex.height !== height;
1787
+ if (resized) {
1788
+ this._backgroundTex?.destroy();
1789
+ this._backgroundTex = this._device.createTexture({
1790
+ format: 'rgba32float',
1791
+ usage: GPUTextureUsage.TEXTURE_BINDING |
1792
+ GPUTextureUsage.COPY_DST |
1793
+ GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING,
1794
+ label: 'Background',
1795
+ size: { width, height, depthOrArrayLayers: 1 }
1796
+ });
1797
+ }
1798
+ if (backgroundImage && 'sky' in backgroundImage) {
1799
+ this._skyBaker ??= new WGPUSkyBaker(this._device);
1800
+ this._skyBaker.bake(backgroundImage, this._backgroundTex);
1801
+ }
1802
+ else if (backgroundImage) {
1673
1803
  const source = backgroundImage.data;
1674
1804
  const data = source instanceof Float32Array
1675
1805
  ? source
1676
- : Float32Array.from(source, (value) => (source instanceof Uint8Array || source instanceof Uint8ClampedArray) ? value / 255 : value);
1806
+ : source instanceof Uint8Array || source instanceof Uint8ClampedArray
1807
+ ? Float32Array.from(source, value => value / 255)
1808
+ : Float32Array.from(source);
1677
1809
  this._device.queue.writeTexture({ texture: this._backgroundTex }, data.buffer, { bytesPerRow: width * 4 * 4 }, { width, height, depthOrArrayLayers: 1 });
1678
1810
  }
1679
- this._needsUpdateBindGroups = true;
1811
+ this._needsUpdateBindGroups ||= resized;
1680
1812
  }
1681
1813
  _updateLightSamplingData() {
1682
- const choices = this._lightChoices;
1683
- choices.reserve(1 + this._analyticLights.length + this._emissiveTriangles.length);
1684
- choices.push(LIGHT_CHOICE_ENVIRONMENT, 0, estimateEnvironmentPower(this._envImageSource));
1685
- this._analyticLights.forEach((light, i) => choices.push(LIGHT_CHOICE_ANALYTIC, i, estimateAnalyticLightPower(light)));
1686
- this._emissiveTriangles.forEach((triangle, i) => choices.push(LIGHT_CHOICE_EMISSIVE_TRIANGLE, i, triangle.area * triangle.samplingLuminance));
1687
- const distribution = buildLightAliasTable(choices);
1688
- const domainDiagnostics = {
1689
- environment: { count: 0, weight: 0, pmf: 0 },
1690
- infinite: { count: 0, weight: 0, pmf: 0 },
1691
- localAndEmissive: { count: 0, weight: 0, pmf: 0 }
1692
- };
1693
- this._analyticLightSelectionPmfs = new Array(this._analyticLights.length).fill(0);
1694
- this._rtUniform.views.envLightPmf[0] = 0;
1695
- const domains = [domainDiagnostics.environment, domainDiagnostics.infinite, domainDiagnostics.localAndEmissive];
1696
- for (let i = 0; i < choices.count; i++) {
1697
- const type = choices.sourceTypes[i], source = choices.sourceIndices[i];
1698
- const pmf = distribution.pmfs[i];
1699
- const domainIndex = type === LIGHT_CHOICE_ENVIRONMENT ? LIGHT_DOMAIN_ENVIRONMENT :
1700
- type === LIGHT_CHOICE_ANALYTIC && this._analyticLights[source]?.type === 'DirectionalLight' ?
1701
- LIGHT_DOMAIN_INFINITE : LIGHT_DOMAIN_LOCAL_AND_EMISSIVE;
1702
- const domain = domains[domainIndex];
1703
- domain.count++;
1704
- domain.weight += choices.weights[i];
1705
- domain.pmf += pmf;
1706
- if (type === LIGHT_CHOICE_ENVIRONMENT)
1707
- this._rtUniform.views.envLightPmf[0] = pmf;
1708
- else if (type === LIGHT_CHOICE_ANALYTIC)
1709
- this._analyticLightSelectionPmfs[source] = pmf;
1710
- else
1711
- this._emissiveTriangles[source].selectionPmf = pmf;
1712
- }
1713
- this._lightDomainDiagnostics = domainDiagnostics;
1714
- this._rtUniform.views.lightChoiceCount[0] = choices.count;
1715
- this._rtUniform.views.lightWeightTotal[0] = distribution.totalWeight;
1716
- const emissiveTexels = this._emissiveTriangles.length * 6;
1717
- const choiceTexels = Math.max(choices.count, 1);
1718
- const profileStartTexel = emissiveTexels + choiceTexels;
1719
- const packedIES = packIESProfiles(this._analyticLights, profileStartTexel);
1720
- this._iesProfileTexelOffsets = packedIES.offsets;
1721
- const totalTexels = Math.max(profileStartTexel + packedIES.texelCount, 1);
1722
- const maxDimension = this._device.limits.maxTextureDimension2D;
1723
- const width = Math.min(maxDimension, Math.max(16, Math.ceil(Math.sqrt(totalTexels) / 16) * 16));
1724
- const height = Math.ceil(totalTexels / width);
1725
- if (height > maxDimension) {
1726
- throw new Error('Too many emissive triangles for the light sampling texture.');
1727
- }
1728
- const resized = !this._lightSamplingTexture || width !== this._lightSamplingTextureSize[0] || height !== this._lightSamplingTextureSize[1];
1814
+ this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map((light) => light.type), this._emissiveTriangles.length);
1815
+ const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource
1816
+ ? proceduralEnvironmentPower(this._envImageSource)
1817
+ : estimateEnvironmentPower(this._envImageSource), this._device.limits.maxTextureDimension2D, this._lightChoices, this._lightSamplingScratch, this._shadowCatcherLightChoices);
1818
+ researchTransport()?.prepare(this, 'webgpu', lighting);
1819
+ this._analyticLightSelectionPmfs = lighting.analyticPmfs;
1820
+ this._iesProfileTexelOffsets = lighting.profiles.offsets;
1821
+ this._lightDomainDiagnostics = lighting.domains;
1822
+ const { lightWidth: width, lightHeight: height, lightData: textureData } = lighting;
1823
+ writeTraceLighting(this._rtUniform.views, lighting);
1824
+ const resized = !this._lightSamplingTexture ||
1825
+ width !== this._lightSamplingTextureSize[0] ||
1826
+ height !== this._lightSamplingTextureSize[1];
1729
1827
  if (!this._lightSamplingTexture ||
1730
1828
  width !== this._lightSamplingTextureSize[0] ||
1731
1829
  height !== this._lightSamplingTextureSize[1]) {
@@ -1739,14 +1837,6 @@ ${rayTraceCompute({
1739
1837
  this._lightSamplingTextureSize = [width, height];
1740
1838
  this._needsUpdateBindGroups = true;
1741
1839
  }
1742
- const textureData = this._lightSamplingScratch?.length === width * height * 4
1743
- ? this._lightSamplingScratch : new Float32Array(width * height * 4);
1744
- textureData.fill(0);
1745
- if (this._emissiveTriangles.length) {
1746
- packEmissiveTriangles(this._emissiveTriangles, textureData);
1747
- }
1748
- new Uint32Array(textureData.buffer).set(distribution.choices, emissiveTexels * 4);
1749
- textureData.set(packedIES.data, profileStartTexel * 4);
1750
1840
  const previous = this._lightSamplingData;
1751
1841
  const full = resized || !previous || this._lightSamplingTriangleCount !== this._emissiveTriangles.length;
1752
1842
  const words = new Uint32Array(textureData.buffer);
@@ -1772,8 +1862,6 @@ ${rayTraceCompute({
1772
1862
  this._lightSamplingScratch = previous;
1773
1863
  this._lightSamplingData = textureData;
1774
1864
  this._lightSamplingTriangleCount = this._emissiveTriangles.length;
1775
- this._rtUniform.views.lightSamplingTextureWidth[0] = width;
1776
- this._rtUniform.views.lightChoiceTexelOffset[0] = emissiveTexels;
1777
1865
  this._needsUpdateAnalyticLightsBuffer = true;
1778
1866
  this._needsUpdateLightSamplingData = false;
1779
1867
  }
@@ -1823,33 +1911,15 @@ ${rayTraceCompute({
1823
1911
  const tlas = buildTLAS(res.instances, res.blases);
1824
1912
  this._tlas = tlas;
1825
1913
  this._uploadBVH(tlas, res.blases);
1826
- const materialData = (this._materialData = new MaterialTable(materials, this._analyticLights, {
1914
+ const materialData = (this._materialData = this._createMaterialData(materials, this._analyticLights, {
1827
1915
  maxTextureArrayBytes: this._maxTextureArrayBytes,
1828
1916
  maxTextureArrayLayers: this._device.limits.maxTextureArrayLayers,
1829
1917
  maxTextureDimension2D: this._device.limits.maxTextureDimension2D
1830
1918
  }));
1831
1919
  this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, materials);
1832
1920
  this._needsUpdateLightSamplingData = true;
1833
- const attrNames = [
1834
- 'position',
1835
- 'normal',
1836
- 'uv',
1837
- 'uv1',
1838
- 'color',
1839
- 'tangent',
1840
- 'materialIndex'
1841
- ];
1842
- const attributes = attrNames.map((a) => mergedGeometry[a]);
1843
- const fixedSize = 4;
1844
- let stride = 0;
1845
- const offsets = attrNames.map((attrName, idx) => {
1846
- const attr = attributes[idx];
1847
- const offset = stride;
1848
- if (attr) {
1849
- stride += fixedSize;
1850
- }
1851
- return offset;
1852
- });
1921
+ const attributeLayout = sceneAttributeLayout(mergedGeometry);
1922
+ const { stride, offsets } = attributeLayout;
1853
1923
  const attrBufferData = new F32(mergedGeometry.vertexCount * stride);
1854
1924
  packVertexAttributes(mergedGeometry, attrBufferData, stride, offsets, 0, mergedGeometry.vertexCount);
1855
1925
  this._attributeData = attrBufferData;
@@ -1869,17 +1939,7 @@ ${rayTraceCompute({
1869
1939
  cwbvh: 8
1870
1940
  };
1871
1941
  this._blasLayoutMask = res.blases.reduce((mask, blas) => mask | blasLayoutBits[blas.bvh.layout ?? 'bvh2'], 0);
1872
- Object.assign(this._constants, {
1873
- TLAS_STACK_SIZE: retainedShaderCapacity(traversalStackSizes.tlas, Number(this._constants.TLAS_STACK_SIZE ?? 0)),
1874
- BLAS_STACK_SIZE: retainedShaderCapacity(traversalStackSizes.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32),
1875
- USE_UV: mergedGeometry.uv != null,
1876
- USE_VERTEX_COLOR: mergedGeometry.color != null,
1877
- ATTR_STRIDE: stride / fixedSize,
1878
- ...offsets.reduce((obj, off, idx) => {
1879
- obj[attrNames[idx].toUpperCase() + '_OFF'] = off / fixedSize;
1880
- return obj;
1881
- }, {})
1882
- });
1942
+ Object.assign(this._constants, sceneTraversalConstants(traversalStackSizes.tlas, traversalStackSizes.blas, this._constants), attributeLayout.constants);
1883
1943
  this._updateMaterialConstants(materials, materialData, mergedGeometry);
1884
1944
  this._updateTextures(materialData);
1885
1945
  this._needsUpdateBindGroups = true;
@@ -1905,52 +1965,11 @@ ${rayTraceCompute({
1905
1965
  }
1906
1966
  this._hasShadowCatcher = materials.some((m) => m.shadowCatcher);
1907
1967
  const hasDisplacement = materialData.getDisplacementMaps().length > 0;
1908
- if (hasDisplacement !== this._hasDisplacement) {
1909
- this._hasDisplacement = hasDisplacement;
1910
- // Needs to recreate pipeline layout
1911
- this._rayTracePipelineLayout = undefined;
1912
- }
1913
- const HAS_BEVEL = materials.some((m) => m.bevelSize > 0);
1914
- const SPECTRAL = materials.some((m) => m.transmissionDispersionScale > 0);
1915
- const HAS_MEDIUM = materials.some(materialUsesMedium);
1916
- // Retain the atlas uniform capacity when a stage/material is removed.
1917
- // Shrinking the array would rebuild every pipeline again on the next edit.
1918
- const NUM_MAPS = Math.max(Math.ceil(+materialData.defines.NUM_MAPS / 10) * 10, Number(this._constants.NUM_MAPS ?? 0), 20);
1919
- // Either layer's map requires independent normals, including a smooth
1920
- // coat over a mapped base. Unmapped materials retain the shared path.
1921
- const USE_COAT_NORMAL_FRAMES = materials.some(m => m.coatWeight > 0 && (!!m.normalMap || !!m.coatNormalMap));
1922
- Object.assign(this._constants, {
1923
- // Match the atlas UV selector. Keep the second set when any texture
1924
- // needs it; material/texture edits already rebuild these constants.
1925
- USE_UV1: materialData.bufferData.mapUvChannel.some(channel => channel > 0.5),
1926
- // Isotropic OpenPBR constructs its own basis. Normal mapping uses
1927
- // triangle UV differentials rather than this vertex tangent.
1928
- USE_TANGENT: mergedGeometry.tangent != null &&
1929
- materials.some(material => material.specularAnisotropy > 0 || material.coatAnisotropy > 0 || (!!material.coatNormalMap && material.coatWeight > 0)),
1930
- USE_COAT_NORMAL_FRAMES,
1931
- USE_ANISOTROPY_DIRECTIONS: USE_COAT_NORMAL_FRAMES || materials.some(m => m.specularAnisotropy > 0 && (m.specularAnisotropyModel === 'gltf' || !!m.anisotropyMap || m.specularAnisotropyRotation !== 0) ||
1932
- m.coatAnisotropy > 0 && m.coatAnisotropyRotation !== 0),
1933
- HAS_MEDIUM,
1934
- HAS_SHADOW_CATCHER: this._hasShadowCatcher,
1935
- HAS_THIN_FILM: materials.some((material) => material.thinFilmWeight > 0),
1936
- HAS_COAT: materials.some((material) => material.coatWeight > 0),
1937
- HAS_FUZZ: materials.some((material) => material.fuzzWeight > 0),
1938
- HAS_SUBSURFACE: materials.some((material) => material.subsurfaceWeight > 0),
1939
- HAS_BEVEL,
1940
- HAS_ALPHA_MASK: materials.some((m) => resolveMaterialAlphaMode(m) === 'mask'),
1941
- HAS_ALPHA_BLEND: materials.some((m) => resolveMaterialAlphaMode(m) === 'blend'),
1942
- HAS_ALPHA: materials.some((m) => resolveMaterialAlphaMode(m) !== 'opaque'),
1943
- SPECTRAL,
1944
- BEVEL_SAMPLES: this._bevelSamples,
1945
- USE_RR: true,
1946
- // Match WebGL: defer roulette for multiple-scattering SSS paths.
1947
- RR_MAX: HAS_MEDIUM ? 12 : 2,
1948
- NUM_UNIFORM_LIGHTS: FIXED_LIGHT_COUNT,
1949
- ...materialData.defines,
1950
- NUM_MAPS,
1951
- ...materialTextureFeatures(materialData.defines),
1952
- USE_NORMAL_MAPS: +materialData.defines.NUM_NORMAL_MAPS > 0
1953
- });
1968
+ this._hasDisplacement = hasDisplacement;
1969
+ Object.assign(this._constants, wgpuMaterialConstants(materials, materialData, mergedGeometry, this._bevelSamples, Number(this._constants.NUM_MAPS ?? 0)));
1970
+ // Runtime storage metadata has no texture-count specialization.
1971
+ if (this._bindless)
1972
+ this._constants.NUM_MAPS = 20;
1954
1973
  }
1955
1974
  _updateGeometryData() {
1956
1975
  const res = this._preparedMeshData, tlas = this._tlas;
@@ -1996,7 +2015,7 @@ ${rayTraceCompute({
1996
2015
  const res = this._preparedMeshData;
1997
2016
  let materialData = this._materialData;
1998
2017
  if (update.level === 'textures') {
1999
- materialData = this._materialData = new MaterialTable(res.materials, this._analyticLights, {
2018
+ materialData = this._materialData = this._createMaterialData(res.materials, this._analyticLights, {
2000
2019
  maxTextureArrayBytes: this._maxTextureArrayBytes,
2001
2020
  maxTextureArrayLayers: this._device.limits.maxTextureArrayLayers,
2002
2021
  maxTextureDimension2D: this._device.limits.maxTextureDimension2D
@@ -2106,7 +2125,26 @@ ${rayTraceCompute({
2106
2125
  this._dirtyTransformMeshes.clear();
2107
2126
  this._needsRefitTLAS = false;
2108
2127
  }
2128
+ _createMaterialData(...args) {
2129
+ if (this._bindless) {
2130
+ const data = new WGPUBindlessMaterialData(args[0], args[1]);
2131
+ const reason = bindlessLimitReason(this._device, data.textures.length);
2132
+ if (!reason)
2133
+ return data;
2134
+ this._textureSelection.fallbackReason = reason;
2135
+ const retired = this._bindless;
2136
+ this._retiredBindless.add(retired);
2137
+ void this._device.queue.onSubmittedWorkDone().catch(() => { }).then(() => { retired.dispose(); this._retiredBindless.delete(retired); });
2138
+ this._bindless = undefined;
2139
+ }
2140
+ return new MaterialTable(...args);
2141
+ }
2109
2142
  _updateTextures(materialData) {
2143
+ if (materialData instanceof WGPUBindlessMaterialData) {
2144
+ this._bindless.update(materialData.textures, this._maxTextureArrayBytes);
2145
+ this._needsUpdateBindGroups = true;
2146
+ return;
2147
+ }
2110
2148
  // Textures
2111
2149
  // TODO Avoid zero length array.
2112
2150
  const mapInfo = materialData.bufferData.mapInfo ?? [0, 0, 0, 0];
@@ -2209,6 +2247,10 @@ ${rayTraceCompute({
2209
2247
  }
2210
2248
  // TODO reuse the code of RayTraceNode
2211
2249
  setCamera(camera) {
2250
+ this._causticPerspective = camera.projection === 'perspective';
2251
+ const projection = camera.getProjectionMatrix(this._targetAspect);
2252
+ this._causticCameraScale = Math.abs(projection[0] * projection[5]) / 4;
2253
+ this._setCaustics(this._causticSettings);
2212
2254
  const viewProjectionInvMat = this._viewProjInv;
2213
2255
  this._apertureRadius = cameraApertureRadius(camera);
2214
2256
  this._focusDistance = camera.focusDistance;
@@ -2268,14 +2310,7 @@ ${rayTraceCompute({
2268
2310
  const arr = this._rtUniform.views.jitter;
2269
2311
  arr[0] = x;
2270
2312
  arr[1] = y;
2271
- this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, new F32([
2272
- ...this._viewProjInv,
2273
- ...this._cameraWorld,
2274
- this._apertureRadius,
2275
- this._focusDistance,
2276
- 0,
2277
- 0
2278
- ]));
2313
+ this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
2279
2314
  }
2280
2315
  setSampleTiles(columns, rows, tileX, tileY) {
2281
2316
  const arr = this._rtUniform.views.scaleOffset;
@@ -2301,9 +2336,18 @@ ${rayTraceCompute({
2301
2336
  return this._hasShadowCatcher;
2302
2337
  }
2303
2338
  dispose() {
2339
+ this._disposed = true;
2340
+ this._envDistribution?.dispose();
2341
+ this._skyBaker?.dispose();
2342
+ this._bindless?.dispose();
2343
+ this._retiredBindless.forEach((textures) => textures.dispose());
2344
+ this._retiredBindless.clear();
2345
+ this._causticLight?.dispose();
2346
+ this._photonMap?.dispose();
2304
2347
  this._shaderCache.clear();
2348
+ this._shaderCapabilities.clear();
2305
2349
  const buffers = new Set([
2306
- ...Array.from(this._targetOutputs.values()).flatMap(target => target.buffers),
2350
+ ...Array.from(this._targetOutputs.values()).flatMap((target) => target.buffers),
2307
2351
  this._mapInfoUniform,
2308
2352
  this._attrBuffers,
2309
2353
  this._materialsBuffer,
@@ -2323,6 +2367,9 @@ ${rayTraceCompute({
2323
2367
  this._adaptiveSamplingTexture
2324
2368
  ]);
2325
2369
  textures.forEach((texture) => texture?.destroy());
2370
+ for (const job of this._queuedCompiles.splice(0))
2371
+ job.release();
2372
+ this._traceVariants.clear();
2326
2373
  this._rayTracePipeline = undefined;
2327
2374
  this._rayTraceBindGroups = undefined;
2328
2375
  }