@meimu/mutracer 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (536) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/Apache-2.0.txt +202 -0
  3. package/LICENSES/GLSL-PathTracer-MIT.txt +21 -0
  4. package/LICENSES/GlMatrix-MIT.txt +19 -0
  5. package/LICENSES/KinoBloom-MIT.txt +21 -0
  6. package/LICENSES/OIDNWeb-MIT.txt +21 -0
  7. package/LICENSES/Octicons-MIT.txt +21 -0
  8. package/LICENSES/RayTracingRenderer-MIT.txt +21 -0
  9. package/LICENSES/TinyBVH-MIT.txt +21 -0
  10. package/LICENSES/WebGPUUtils-MIT.txt +21 -0
  11. package/LICENSES/ZetaRay-MIT.txt +21 -0
  12. package/README.md +195 -2
  13. package/THIRD_PARTY_NOTICES.md +160 -0
  14. package/dist/assets/bvh.worker.js +1 -0
  15. package/dist/mutracer.umd.js +14352 -0
  16. package/docs/api.md +394 -0
  17. package/docs/bvh.md +141 -0
  18. package/docs/denoising.md +301 -0
  19. package/docs/getting-started.md +308 -0
  20. package/docs/materials.md +281 -0
  21. package/docs/performance.md +85 -0
  22. package/docs/post-processing.md +87 -0
  23. package/docs/progressive-rendering.md +144 -0
  24. package/docs/sampling.md +40 -0
  25. package/docs/threejs.md +273 -0
  26. package/docs/todo.md +61 -0
  27. package/docs/updates.md +208 -0
  28. package/lib/Display.d.ts +72 -0
  29. package/lib/Display.js +121 -0
  30. package/lib/Display.js.map +1 -0
  31. package/lib/Engine.d.ts +161 -0
  32. package/lib/Engine.js +681 -0
  33. package/lib/Engine.js.map +1 -0
  34. package/lib/Render.d.ts +256 -0
  35. package/lib/Render.js +671 -0
  36. package/lib/Render.js.map +1 -0
  37. package/lib/RenderLoop.d.ts +76 -0
  38. package/lib/RenderLoop.js +204 -0
  39. package/lib/RenderLoop.js.map +1 -0
  40. package/lib/Sync.d.ts +19 -0
  41. package/lib/Sync.js +19 -0
  42. package/lib/Sync.js.map +1 -0
  43. package/lib/constants.d.ts +2 -0
  44. package/lib/constants.js +3 -0
  45. package/lib/constants.js.map +1 -0
  46. package/lib/main.d.ts +56 -0
  47. package/lib/main.js +43 -0
  48. package/lib/main.js.map +1 -0
  49. package/lib/math.d.ts +23 -0
  50. package/lib/math.js +56 -0
  51. package/lib/math.js.map +1 -0
  52. package/lib/oidn.d.ts +45 -0
  53. package/lib/oidn.js +42 -0
  54. package/lib/oidn.js.map +1 -0
  55. package/lib/oidnBundledWeights.cdn.d.ts +2 -0
  56. package/lib/oidnBundledWeights.cdn.js +5 -0
  57. package/lib/oidnBundledWeights.cdn.js.map +1 -0
  58. package/lib/oidnBundledWeights.d.ts +2 -0
  59. package/lib/oidnBundledWeights.js +4 -0
  60. package/lib/oidnBundledWeights.js.map +1 -0
  61. package/lib/renderer/DisplayPostProcess.d.ts +14 -0
  62. package/lib/renderer/DisplayPostProcess.js +41 -0
  63. package/lib/renderer/DisplayPostProcess.js.map +1 -0
  64. package/lib/renderer/MaterialTable.d.ts +119 -0
  65. package/lib/renderer/MaterialTable.js +550 -0
  66. package/lib/renderer/MaterialTable.js.map +1 -0
  67. package/lib/renderer/OfflineAdaptiveSampling.d.ts +47 -0
  68. package/lib/renderer/OfflineAdaptiveSampling.js +36 -0
  69. package/lib/renderer/OfflineAdaptiveSampling.js.map +1 -0
  70. package/lib/renderer/RenderingPipeline.d.ts +232 -0
  71. package/lib/renderer/RenderingPipeline.js +18 -0
  72. package/lib/renderer/RenderingPipeline.js.map +1 -0
  73. package/lib/renderer/ScratchPool.d.ts +13 -0
  74. package/lib/renderer/ScratchPool.js +21 -0
  75. package/lib/renderer/ScratchPool.js.map +1 -0
  76. package/lib/renderer/TileRender.d.ts +44 -0
  77. package/lib/renderer/TileRender.js +283 -0
  78. package/lib/renderer/TileRender.js.map +1 -0
  79. package/lib/renderer/WGPUBloomPass.d.ts +17 -0
  80. package/lib/renderer/WGPUBloomPass.js +243 -0
  81. package/lib/renderer/WGPUBloomPass.js.map +1 -0
  82. package/lib/renderer/WGPUCanvasSurface.d.ts +21 -0
  83. package/lib/renderer/WGPUCanvasSurface.js +48 -0
  84. package/lib/renderer/WGPUCanvasSurface.js.map +1 -0
  85. package/lib/renderer/WGPUComputePass.d.ts +73 -0
  86. package/lib/renderer/WGPUComputePass.js +317 -0
  87. package/lib/renderer/WGPUComputePass.js.map +1 -0
  88. package/lib/renderer/WGPUFullQuadPass.d.ts +73 -0
  89. package/lib/renderer/WGPUFullQuadPass.js +389 -0
  90. package/lib/renderer/WGPUFullQuadPass.js.map +1 -0
  91. package/lib/renderer/WGPUHDRPeakMeter.d.ts +19 -0
  92. package/lib/renderer/WGPUHDRPeakMeter.js +112 -0
  93. package/lib/renderer/WGPUHDRPeakMeter.js.map +1 -0
  94. package/lib/renderer/WGPUJNLMDenoiser.d.ts +61 -0
  95. package/lib/renderer/WGPUJNLMDenoiser.js +412 -0
  96. package/lib/renderer/WGPUJNLMDenoiser.js.map +1 -0
  97. package/lib/renderer/WGPURayTraceOutput.d.ts +44 -0
  98. package/lib/renderer/WGPURayTraceOutput.js +368 -0
  99. package/lib/renderer/WGPURayTraceOutput.js.map +1 -0
  100. package/lib/renderer/WGPURayTracePass.d.ts +284 -0
  101. package/lib/renderer/WGPURayTracePass.js +2323 -0
  102. package/lib/renderer/WGPURayTracePass.js.map +1 -0
  103. package/lib/renderer/WGPURenderingPipeline.d.ts +9 -0
  104. package/lib/renderer/WGPURenderingPipeline.js +1446 -0
  105. package/lib/renderer/WGPURenderingPipeline.js.map +1 -0
  106. package/lib/renderer/WGPUSceneFeatures.d.ts +3 -0
  107. package/lib/renderer/WGPUSceneFeatures.js +20 -0
  108. package/lib/renderer/WGPUSceneFeatures.js.map +1 -0
  109. package/lib/renderer/WGPUScratchBuffer.d.ts +9 -0
  110. package/lib/renderer/WGPUScratchBuffer.js +21 -0
  111. package/lib/renderer/WGPUScratchBuffer.js.map +1 -0
  112. package/lib/renderer/WGPUScratchTexture.d.ts +10 -0
  113. package/lib/renderer/WGPUScratchTexture.js +27 -0
  114. package/lib/renderer/WGPUScratchTexture.js.map +1 -0
  115. package/lib/renderer/WGPUShaderCache.d.ts +26 -0
  116. package/lib/renderer/WGPUShaderCache.js +103 -0
  117. package/lib/renderer/WGPUShaderCache.js.map +1 -0
  118. package/lib/renderer/WebGLBloomPass.d.ts +18 -0
  119. package/lib/renderer/WebGLBloomPass.js +113 -0
  120. package/lib/renderer/WebGLBloomPass.js.map +1 -0
  121. package/lib/renderer/WebGLFullQuadPass.d.ts +65 -0
  122. package/lib/renderer/WebGLFullQuadPass.js +208 -0
  123. package/lib/renderer/WebGLFullQuadPass.js.map +1 -0
  124. package/lib/renderer/WebGLJNLMDenoiser.d.ts +32 -0
  125. package/lib/renderer/WebGLJNLMDenoiser.js +328 -0
  126. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -0
  127. package/lib/renderer/WebGLRayTraceOutputPass.d.ts +20 -0
  128. package/lib/renderer/WebGLRayTraceOutputPass.js +106 -0
  129. package/lib/renderer/WebGLRayTraceOutputPass.js.map +1 -0
  130. package/lib/renderer/WebGLRayTracePass.d.ts +142 -0
  131. package/lib/renderer/WebGLRayTracePass.js +845 -0
  132. package/lib/renderer/WebGLRayTracePass.js.map +1 -0
  133. package/lib/renderer/WebGLReadback.d.ts +4 -0
  134. package/lib/renderer/WebGLReadback.js +75 -0
  135. package/lib/renderer/WebGLReadback.js.map +1 -0
  136. package/lib/renderer/WebGLRenderingPipeline.d.ts +4 -0
  137. package/lib/renderer/WebGLRenderingPipeline.js +510 -0
  138. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -0
  139. package/lib/renderer/WebGLScratchCopy.d.ts +10 -0
  140. package/lib/renderer/WebGLScratchCopy.js +27 -0
  141. package/lib/renderer/WebGLScratchCopy.js.map +1 -0
  142. package/lib/renderer/accumulationTarget.d.ts +26 -0
  143. package/lib/renderer/accumulationTarget.js +2 -0
  144. package/lib/renderer/accumulationTarget.js.map +1 -0
  145. package/lib/renderer/binnedSahBvh.d.ts +31 -0
  146. package/lib/renderer/binnedSahBvh.js +263 -0
  147. package/lib/renderer/binnedSahBvh.js.map +1 -0
  148. package/lib/renderer/bvh.worker.d.ts +1 -0
  149. package/lib/renderer/bvh.worker.js +16 -0
  150. package/lib/renderer/bvh.worker.js.map +1 -0
  151. package/lib/renderer/bvhAccel.d.ts +81 -0
  152. package/lib/renderer/bvhAccel.js +621 -0
  153. package/lib/renderer/bvhAccel.js.map +1 -0
  154. package/lib/renderer/bvhWorkerBuild.d.ts +12 -0
  155. package/lib/renderer/bvhWorkerBuild.js +17 -0
  156. package/lib/renderer/bvhWorkerBuild.js.map +1 -0
  157. package/lib/renderer/bvhWorkerClient.d.ts +57 -0
  158. package/lib/renderer/bvhWorkerClient.js +166 -0
  159. package/lib/renderer/bvhWorkerClient.js.map +1 -0
  160. package/lib/renderer/cameraLens.d.ts +5 -0
  161. package/lib/renderer/cameraLens.js +15 -0
  162. package/lib/renderer/cameraLens.js.map +1 -0
  163. package/lib/renderer/cameraSnapshot.d.ts +3 -0
  164. package/lib/renderer/cameraSnapshot.js +7 -0
  165. package/lib/renderer/cameraSnapshot.js.map +1 -0
  166. package/lib/renderer/committedScene.d.ts +13 -0
  167. package/lib/renderer/committedScene.js +121 -0
  168. package/lib/renderer/committedScene.js.map +1 -0
  169. package/lib/renderer/dataTextureUtil.d.ts +32 -0
  170. package/lib/renderer/dataTextureUtil.js +46 -0
  171. package/lib/renderer/dataTextureUtil.js.map +1 -0
  172. package/lib/renderer/denoiser.d.ts +65 -0
  173. package/lib/renderer/denoiser.js +2 -0
  174. package/lib/renderer/denoiser.js.map +1 -0
  175. package/lib/renderer/depthQuery.d.ts +14 -0
  176. package/lib/renderer/depthQuery.js +14 -0
  177. package/lib/renderer/depthQuery.js.map +1 -0
  178. package/lib/renderer/deviceRequirements.d.ts +23 -0
  179. package/lib/renderer/deviceRequirements.js +50 -0
  180. package/lib/renderer/deviceRequirements.js.map +1 -0
  181. package/lib/renderer/envMapCreation.d.ts +10 -0
  182. package/lib/renderer/envMapCreation.js +96 -0
  183. package/lib/renderer/envMapCreation.js.map +1 -0
  184. package/lib/renderer/envMapDistribution.d.ts +9 -0
  185. package/lib/renderer/envMapDistribution.js +107 -0
  186. package/lib/renderer/envMapDistribution.js.map +1 -0
  187. package/lib/renderer/float16.d.ts +1 -0
  188. package/lib/renderer/float16.js +19 -0
  189. package/lib/renderer/float16.js.map +1 -0
  190. package/lib/renderer/geometryBvh.d.ts +11 -0
  191. package/lib/renderer/geometryBvh.js +24 -0
  192. package/lib/renderer/geometryBvh.js.map +1 -0
  193. package/lib/renderer/geometryUpdates.d.ts +28 -0
  194. package/lib/renderer/geometryUpdates.js +131 -0
  195. package/lib/renderer/geometryUpdates.js.map +1 -0
  196. package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +2 -0
  197. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +37 -0
  198. package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -0
  199. package/lib/renderer/glsl/chunks/common.glsl.d.ts +2 -0
  200. package/lib/renderer/glsl/chunks/common.glsl.js +17 -0
  201. package/lib/renderer/glsl/chunks/common.glsl.js.map +1 -0
  202. package/lib/renderer/glsl/chunks/envMap.glsl.d.ts +2 -0
  203. package/lib/renderer/glsl/chunks/envMap.glsl.js +192 -0
  204. package/lib/renderer/glsl/chunks/envMap.glsl.js.map +1 -0
  205. package/lib/renderer/glsl/chunks/intersect.glsl.d.ts +2 -0
  206. package/lib/renderer/glsl/chunks/intersect.glsl.js +1091 -0
  207. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -0
  208. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +2 -0
  209. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +371 -0
  210. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -0
  211. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.d.ts +2 -0
  212. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +1767 -0
  213. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js.map +1 -0
  214. package/lib/renderer/glsl/chunks/rand.glsl.d.ts +2 -0
  215. package/lib/renderer/glsl/chunks/rand.glsl.js +26 -0
  216. package/lib/renderer/glsl/chunks/rand.glsl.js.map +1 -0
  217. package/lib/renderer/glsl/chunks/random.glsl.d.ts +2 -0
  218. package/lib/renderer/glsl/chunks/random.glsl.js +27 -0
  219. package/lib/renderer/glsl/chunks/random.glsl.js.map +1 -0
  220. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +2 -0
  221. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +287 -0
  222. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -0
  223. package/lib/renderer/glsl/chunks/sampleLight.glsl.d.ts +2 -0
  224. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +254 -0
  225. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -0
  226. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +2 -0
  227. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +279 -0
  228. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -0
  229. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.d.ts +2 -0
  230. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +48 -0
  231. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js.map +1 -0
  232. package/lib/renderer/glsl/chunks/sampling.glsl.d.ts +2 -0
  233. package/lib/renderer/glsl/chunks/sampling.glsl.js +194 -0
  234. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -0
  235. package/lib/renderer/glsl/chunks/sobol.glsl.d.ts +2 -0
  236. package/lib/renderer/glsl/chunks/sobol.glsl.js +170 -0
  237. package/lib/renderer/glsl/chunks/sobol.glsl.js.map +1 -0
  238. package/lib/renderer/glsl/chunks/textureLinear.glsl.d.ts +2 -0
  239. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +27 -0
  240. package/lib/renderer/glsl/chunks/textureLinear.glsl.js.map +1 -0
  241. package/lib/renderer/glsl/chunks/uvProjection.glsl.d.ts +2 -0
  242. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +72 -0
  243. package/lib/renderer/glsl/chunks/uvProjection.glsl.js.map +1 -0
  244. package/lib/renderer/glsl/depthQuery.frag.d.ts +3 -0
  245. package/lib/renderer/glsl/depthQuery.frag.js +55 -0
  246. package/lib/renderer/glsl/depthQuery.frag.js.map +1 -0
  247. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.d.ts +6 -0
  248. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +46 -0
  249. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js.map +1 -0
  250. package/lib/renderer/glsl/rayTrace.frag.d.ts +2 -0
  251. package/lib/renderer/glsl/rayTrace.frag.js +619 -0
  252. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -0
  253. package/lib/renderer/glsl/shadowCatcher.frag.d.ts +3 -0
  254. package/lib/renderer/glsl/shadowCatcher.frag.js +119 -0
  255. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -0
  256. package/lib/renderer/glsl/webglPipeline.frag.d.ts +4 -0
  257. package/lib/renderer/glsl/webglPipeline.frag.js +208 -0
  258. package/lib/renderer/glsl/webglPipeline.frag.js.map +1 -0
  259. package/lib/renderer/gpuMemory.d.ts +2 -0
  260. package/lib/renderer/gpuMemory.js +10 -0
  261. package/lib/renderer/gpuMemory.js.map +1 -0
  262. package/lib/renderer/halton.d.ts +1 -0
  263. package/lib/renderer/halton.js +15 -0
  264. package/lib/renderer/halton.js.map +1 -0
  265. package/lib/renderer/jnlm.d.ts +85 -0
  266. package/lib/renderer/jnlm.js +87 -0
  267. package/lib/renderer/jnlm.js.map +1 -0
  268. package/lib/renderer/lightSampling.d.ts +75 -0
  269. package/lib/renderer/lightSampling.js +305 -0
  270. package/lib/renderer/lightSampling.js.map +1 -0
  271. package/lib/renderer/lightsUtil.d.ts +26 -0
  272. package/lib/renderer/lightsUtil.js +154 -0
  273. package/lib/renderer/lightsUtil.js.map +1 -0
  274. package/lib/renderer/materialChanges.d.ts +13 -0
  275. package/lib/renderer/materialChanges.js +59 -0
  276. package/lib/renderer/materialChanges.js.map +1 -0
  277. package/lib/renderer/materialComparison.generated.d.ts +5 -0
  278. package/lib/renderer/materialComparison.generated.js +655 -0
  279. package/lib/renderer/materialComparison.generated.js.map +1 -0
  280. package/lib/renderer/materialConstants.d.ts +6 -0
  281. package/lib/renderer/materialConstants.js +3 -0
  282. package/lib/renderer/materialConstants.js.map +1 -0
  283. package/lib/renderer/materialEmission.d.ts +9 -0
  284. package/lib/renderer/materialEmission.js +21 -0
  285. package/lib/renderer/materialEmission.js.map +1 -0
  286. package/lib/renderer/materialIndices.d.ts +7 -0
  287. package/lib/renderer/materialIndices.js +34 -0
  288. package/lib/renderer/materialIndices.js.map +1 -0
  289. package/lib/renderer/materialLayout.d.ts +102 -0
  290. package/lib/renderer/materialLayout.js +189 -0
  291. package/lib/renderer/materialLayout.js.map +1 -0
  292. package/lib/renderer/materialTextureSlots.d.ts +537 -0
  293. package/lib/renderer/materialTextureSlots.js +219 -0
  294. package/lib/renderer/materialTextureSlots.js.map +1 -0
  295. package/lib/renderer/mergeMeshesToGeometry.d.ts +8 -0
  296. package/lib/renderer/mergeMeshesToGeometry.js +236 -0
  297. package/lib/renderer/mergeMeshesToGeometry.js.map +1 -0
  298. package/lib/renderer/meshInstances.d.ts +23 -0
  299. package/lib/renderer/meshInstances.js +66 -0
  300. package/lib/renderer/meshInstances.js.map +1 -0
  301. package/lib/renderer/meshMembership.d.ts +10 -0
  302. package/lib/renderer/meshMembership.js +72 -0
  303. package/lib/renderer/meshMembership.js.map +1 -0
  304. package/lib/renderer/oidnNetwork.d.ts +20 -0
  305. package/lib/renderer/oidnNetwork.js +123 -0
  306. package/lib/renderer/oidnNetwork.js.map +1 -0
  307. package/lib/renderer/openPbrEnergyLut.d.ts +32 -0
  308. package/lib/renderer/openPbrEnergyLut.js +965 -0
  309. package/lib/renderer/openPbrEnergyLut.js.map +1 -0
  310. package/lib/renderer/pathLimits.d.ts +4 -0
  311. package/lib/renderer/pathLimits.js +7 -0
  312. package/lib/renderer/pathLimits.js.map +1 -0
  313. package/lib/renderer/prepareMeshBVH.d.ts +110 -0
  314. package/lib/renderer/prepareMeshBVH.js +857 -0
  315. package/lib/renderer/prepareMeshBVH.js.map +1 -0
  316. package/lib/renderer/progressiveScheduler.d.ts +11 -0
  317. package/lib/renderer/progressiveScheduler.js +19 -0
  318. package/lib/renderer/progressiveScheduler.js.map +1 -0
  319. package/lib/renderer/renderPipeline.d.ts +57 -0
  320. package/lib/renderer/renderPipeline.js +49 -0
  321. package/lib/renderer/renderPipeline.js.map +1 -0
  322. package/lib/renderer/sampling.d.ts +10 -0
  323. package/lib/renderer/sampling.js +28 -0
  324. package/lib/renderer/sampling.js.map +1 -0
  325. package/lib/renderer/sceneChangeDetector.d.ts +29 -0
  326. package/lib/renderer/sceneChangeDetector.js +232 -0
  327. package/lib/renderer/sceneChangeDetector.js.map +1 -0
  328. package/lib/renderer/sceneLightChanges.d.ts +14 -0
  329. package/lib/renderer/sceneLightChanges.js +34 -0
  330. package/lib/renderer/sceneLightChanges.js.map +1 -0
  331. package/lib/renderer/scratchSampling.d.ts +3 -0
  332. package/lib/renderer/scratchSampling.js +39 -0
  333. package/lib/renderer/scratchSampling.js.map +1 -0
  334. package/lib/renderer/shader.d.ts +37 -0
  335. package/lib/renderer/shader.js +41 -0
  336. package/lib/renderer/shader.js.map +1 -0
  337. package/lib/renderer/staticBvhPreparation.d.ts +24 -0
  338. package/lib/renderer/staticBvhPreparation.js +116 -0
  339. package/lib/renderer/staticBvhPreparation.js.map +1 -0
  340. package/lib/renderer/textureRegion.d.ts +8 -0
  341. package/lib/renderer/textureRegion.js +9 -0
  342. package/lib/renderer/textureRegion.js.map +1 -0
  343. package/lib/renderer/texturesFromMaterials.d.ts +6 -0
  344. package/lib/renderer/texturesFromMaterials.js +37 -0
  345. package/lib/renderer/texturesFromMaterials.js.map +1 -0
  346. package/lib/renderer/util.d.ts +17 -0
  347. package/lib/renderer/util.js +56 -0
  348. package/lib/renderer/util.js.map +1 -0
  349. package/lib/renderer/webgl.d.ts +131 -0
  350. package/lib/renderer/webgl.js +586 -0
  351. package/lib/renderer/webgl.js.map +1 -0
  352. package/lib/renderer/webglShaders.d.ts +4 -0
  353. package/lib/renderer/webglShaders.js +41 -0
  354. package/lib/renderer/webglShaders.js.map +1 -0
  355. package/lib/renderer/wgsl/chunks/common.wgsl.d.ts +2 -0
  356. package/lib/renderer/wgsl/chunks/common.wgsl.js +23 -0
  357. package/lib/renderer/wgsl/chunks/common.wgsl.js.map +1 -0
  358. package/lib/renderer/wgsl/chunks/envMap.wgsl.d.ts +2 -0
  359. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +128 -0
  360. package/lib/renderer/wgsl/chunks/envMap.wgsl.js.map +1 -0
  361. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -0
  362. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +1441 -0
  363. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -0
  364. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +8 -0
  365. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +85 -0
  366. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -0
  367. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +7 -0
  368. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +315 -0
  369. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -0
  370. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.d.ts +3 -0
  371. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +1737 -0
  372. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js.map +1 -0
  373. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +4 -0
  374. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +352 -0
  375. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -0
  376. package/lib/renderer/wgsl/chunks/rand.wgsl.d.ts +2 -0
  377. package/lib/renderer/wgsl/chunks/rand.wgsl.js +25 -0
  378. package/lib/renderer/wgsl/chunks/rand.wgsl.js.map +1 -0
  379. package/lib/renderer/wgsl/chunks/random.wgsl.d.ts +1 -0
  380. package/lib/renderer/wgsl/chunks/random.wgsl.js +2 -0
  381. package/lib/renderer/wgsl/chunks/random.wgsl.js.map +1 -0
  382. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +5 -0
  383. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +328 -0
  384. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -0
  385. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +2 -0
  386. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +400 -0
  387. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -0
  388. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +3 -0
  389. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +439 -0
  390. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -0
  391. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +2 -0
  392. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +40 -0
  393. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -0
  394. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +2 -0
  395. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +198 -0
  396. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -0
  397. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.d.ts +8 -0
  398. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js +16 -0
  399. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js.map +1 -0
  400. package/lib/renderer/wgsl/chunks/sobol.wgsl.d.ts +2 -0
  401. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +170 -0
  402. package/lib/renderer/wgsl/chunks/sobol.wgsl.js.map +1 -0
  403. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.d.ts +2 -0
  404. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +42 -0
  405. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js.map +1 -0
  406. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +5 -0
  407. package/lib/renderer/wgsl/compose.frag.wgsl.js +218 -0
  408. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -0
  409. package/lib/renderer/wgsl/customDenoise.wgsl.d.ts +4 -0
  410. package/lib/renderer/wgsl/customDenoise.wgsl.js +28 -0
  411. package/lib/renderer/wgsl/customDenoise.wgsl.js.map +1 -0
  412. package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +3 -0
  413. package/lib/renderer/wgsl/depthQuery.wgsl.js +34 -0
  414. package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -0
  415. package/lib/renderer/wgsl/offlineAdaptive.compute.d.ts +2 -0
  416. package/lib/renderer/wgsl/offlineAdaptive.compute.js +108 -0
  417. package/lib/renderer/wgsl/offlineAdaptive.compute.js.map +1 -0
  418. package/lib/renderer/wgsl/rayTrace.compute.d.ts +16 -0
  419. package/lib/renderer/wgsl/rayTrace.compute.js +923 -0
  420. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -0
  421. package/lib/scene/AmbientLight.d.ts +17 -0
  422. package/lib/scene/AmbientLight.js +15 -0
  423. package/lib/scene/AmbientLight.js.map +1 -0
  424. package/lib/scene/Camera.d.ts +78 -0
  425. package/lib/scene/Camera.js +111 -0
  426. package/lib/scene/Camera.js.map +1 -0
  427. package/lib/scene/DirectionalLight.d.ts +18 -0
  428. package/lib/scene/DirectionalLight.js +17 -0
  429. package/lib/scene/DirectionalLight.js.map +1 -0
  430. package/lib/scene/EnvironmentLight.d.ts +14 -0
  431. package/lib/scene/EnvironmentLight.js +14 -0
  432. package/lib/scene/EnvironmentLight.js.map +1 -0
  433. package/lib/scene/Geometry.d.ts +87 -0
  434. package/lib/scene/Geometry.js +108 -0
  435. package/lib/scene/Geometry.js.map +1 -0
  436. package/lib/scene/IESProfile.d.ts +19 -0
  437. package/lib/scene/IESProfile.js +165 -0
  438. package/lib/scene/IESProfile.js.map +1 -0
  439. package/lib/scene/InstancedMesh.d.ts +43 -0
  440. package/lib/scene/InstancedMesh.js +79 -0
  441. package/lib/scene/InstancedMesh.js.map +1 -0
  442. package/lib/scene/Mesh.d.ts +14 -0
  443. package/lib/scene/Mesh.js +17 -0
  444. package/lib/scene/Mesh.js.map +1 -0
  445. package/lib/scene/OpenPBRMaterial.d.ts +224 -0
  446. package/lib/scene/OpenPBRMaterial.js +240 -0
  447. package/lib/scene/OpenPBRMaterial.js.map +1 -0
  448. package/lib/scene/RectLight.d.ts +31 -0
  449. package/lib/scene/RectLight.js +26 -0
  450. package/lib/scene/RectLight.js.map +1 -0
  451. package/lib/scene/Scene.d.ts +55 -0
  452. package/lib/scene/Scene.js +2 -0
  453. package/lib/scene/Scene.js.map +1 -0
  454. package/lib/scene/SceneNode.d.ts +16 -0
  455. package/lib/scene/SceneNode.js +33 -0
  456. package/lib/scene/SceneNode.js.map +1 -0
  457. package/lib/scene/SphereLight.d.ts +22 -0
  458. package/lib/scene/SphereLight.js +20 -0
  459. package/lib/scene/SphereLight.js.map +1 -0
  460. package/lib/scene/SpotLight.d.ts +35 -0
  461. package/lib/scene/SpotLight.js +29 -0
  462. package/lib/scene/SpotLight.js.map +1 -0
  463. package/lib/scene/Texture.d.ts +38 -0
  464. package/lib/scene/Texture.js +37 -0
  465. package/lib/scene/Texture.js.map +1 -0
  466. package/lib/scene/anisotropyParameters.d.ts +1 -0
  467. package/lib/scene/anisotropyParameters.js +13 -0
  468. package/lib/scene/anisotropyParameters.js.map +1 -0
  469. package/lib/scene/geometryChanges.d.ts +5 -0
  470. package/lib/scene/geometryChanges.js +18 -0
  471. package/lib/scene/geometryChanges.js.map +1 -0
  472. package/lib/scene/materialTextureNames.d.ts +2 -0
  473. package/lib/scene/materialTextureNames.js +9 -0
  474. package/lib/scene/materialTextureNames.js.map +1 -0
  475. package/lib/scene/openPbrSchema.d.ts +400 -0
  476. package/lib/scene/openPbrSchema.js +149 -0
  477. package/lib/scene/openPbrSchema.js.map +1 -0
  478. package/lib/scene/thinFilmParameters.d.ts +4 -0
  479. package/lib/scene/thinFilmParameters.js +12 -0
  480. package/lib/scene/thinFilmParameters.js.map +1 -0
  481. package/lib/settings/display.d.ts +118 -0
  482. package/lib/settings/display.js +34 -0
  483. package/lib/settings/display.js.map +1 -0
  484. package/lib/settings/engine.d.ts +37 -0
  485. package/lib/settings/engine.js +26 -0
  486. package/lib/settings/engine.js.map +1 -0
  487. package/lib/settings/render.d.ts +178 -0
  488. package/lib/settings/render.js +36 -0
  489. package/lib/settings/render.js.map +1 -0
  490. package/lib/tinybvh/LICENSE +21 -0
  491. package/lib/tinybvh/TinyBvhBuilder.d.ts +17 -0
  492. package/lib/tinybvh/TinyBvhBuilder.js +211 -0
  493. package/lib/tinybvh/TinyBvhBuilder.js.map +1 -0
  494. package/lib/tinybvh/tinybvh.wasm +0 -0
  495. package/lib/types.d.ts +13 -0
  496. package/lib/types.js +2 -0
  497. package/lib/types.js.map +1 -0
  498. package/lib/util/canvas.d.ts +2 -0
  499. package/lib/util/canvas.js +13 -0
  500. package/lib/util/canvas.js.map +1 -0
  501. package/lib/util/commonGeometry.d.ts +15 -0
  502. package/lib/util/commonGeometry.js +240 -0
  503. package/lib/util/commonGeometry.js.map +1 -0
  504. package/lib/util/events.d.ts +7 -0
  505. package/lib/util/events.js +23 -0
  506. package/lib/util/events.js.map +1 -0
  507. package/lib/util/frame.d.ts +2 -0
  508. package/lib/util/frame.js +10 -0
  509. package/lib/util/frame.js.map +1 -0
  510. package/lib/util/fromTHREE.d.ts +100 -0
  511. package/lib/util/fromTHREE.js +1063 -0
  512. package/lib/util/fromTHREE.js.map +1 -0
  513. package/lib/util/geometry.d.ts +13 -0
  514. package/lib/util/geometry.js +292 -0
  515. package/lib/util/geometry.js.map +1 -0
  516. package/lib/util/loadRGBE.d.ts +7 -0
  517. package/lib/util/loadRGBE.js +168 -0
  518. package/lib/util/loadRGBE.js.map +1 -0
  519. package/lib/util/math.d.ts +8 -0
  520. package/lib/util/math.js +64 -0
  521. package/lib/util/math.js.map +1 -0
  522. package/lib/util/mesh.d.ts +7 -0
  523. package/lib/util/mesh.js +20 -0
  524. package/lib/util/mesh.js.map +1 -0
  525. package/lib/util/threeDeformation.d.ts +20 -0
  526. package/lib/util/threeDeformation.js +218 -0
  527. package/lib/util/threeDeformation.js.map +1 -0
  528. package/lib/version.d.ts +2 -0
  529. package/lib/version.js +3 -0
  530. package/lib/version.js.map +1 -0
  531. package/llms.txt +62 -0
  532. package/oidn/weights/LICENSE.txt +202 -0
  533. package/oidn/weights/README.md +43 -0
  534. package/oidn/weights/rt_hdr_alb_nrm.tza +0 -0
  535. package/oidn/weights/rt_hdr_alb_nrm_small.tza +0 -0
  536. package/package.json +124 -4
@@ -0,0 +1,2323 @@
1
+ import { constants } from './webgl.js';
2
+ import { MATERIAL_FLOAT_COUNT } from './materialLayout.js';
3
+ import { normalizeBounces } from './pathLimits.js';
4
+ import { MATERIAL_TEXTURE_DEFINES, materialTextureFeatures } from './materialTextureSlots.js';
5
+ import { updateMeshInstanceColors } from './prepareMeshBVH.js';
6
+ import { updateTLASInstanceColors } from './bvhAccel.js';
7
+ import { getMaterialHashKey } from '../scene/openPbrSchema.js';
8
+ import { sameInstanceMembership } from './meshInstances.js';
9
+ import InstancedMesh from '../scene/InstancedMesh.js';
10
+ import { dirtyMeshInstanceIndices } from './prepareMeshBVH.js';
11
+ import { cameraApertureRadius } from './cameraLens.js';
12
+ import { getRectLightUOrV, getRectLightArea, getSphereLightArea, getSpotLightArea, getSpotLightRadiance, getLightDirection, getRigidLightFrame, validateSceneLights } from './lightsUtil.js';
13
+ import { getGeometryBvh, setGeometryBvh } from './geometryBvh.js';
14
+ import { SHADOW_CATCHER_ACCUMULATION_BYTES } from './wgsl/chunks/shadowCatcherAccumulation.wgsl.js';
15
+ import { samplingPatternCode, zSobolLog2SPP } from './sampling.js';
16
+ import { DEPTH_SAMPLE_COUNT, packDepthSampleArea, unpackDepthSamples } from './depthQuery.js';
17
+ import { MeshMembershipCache } from './meshMembership.js';
18
+ import { mergeRefitRanges } from './bvhAccel.js';
19
+ import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, packVertexAttributes } from './geometryUpdates.js';
20
+ import { SceneEnvironmentCache, RectLightTextureTracker } from './sceneLightChanges.js';
21
+ import { MaterialChangeTracker, materialIsEmissive } from './materialChanges.js';
22
+ import { WGPUShaderCache, retainedShaderCapacity } from './WGPUShaderCache.js';
23
+ import { WGPU_SCENE_FEATURE_BITS, wgpuSceneFeatureFlags } from './WGPUSceneFeatures.js';
24
+ import { mat4 } from '../math.js';
25
+ import MaterialTable, { DEFAULT_MAX_TEXTURE_ARRAY_BYTES, materialUsesMedium } from './MaterialTable.js';
26
+ import { numberArraysEqual } from './util.js';
27
+ import { generateBackgroundMapFromSceneBackground } 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';
30
+ import { envMapDistribution, environmentMapSourcesMatch } from './envMapDistribution.js';
31
+ import { updateMeshInstances, updateMeshInstanceTransforms } from './prepareMeshBVH.js';
32
+ import { buildTLAS, concatBLASBuffers, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
33
+ import { isTextureDataSource } from '../scene/Texture.js';
34
+ import { resolveMaterialAlphaMode } from '../scene/OpenPBRMaterial.js';
35
+ 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';
37
+ import { getOpenPbrEnergyLutData, OPENPBR_ENERGY_LUT_HEIGHT, OPENPBR_ENERGY_LUT_WIDTH } from './openPbrEnergyLut.js';
38
+ import { offlineAdaptiveCompute } from './wgsl/offlineAdaptive.compute.js';
39
+ import { normalizeOfflineAdaptiveSamplingOptions } from './OfflineAdaptiveSampling.js';
40
+ const F32 = Float32Array;
41
+ const FIXED_LIGHT_COUNT = 64;
42
+ const PIXELS_PER_LIGHT = 6;
43
+ /**
44
+ * WebGPU errors are Web IDL objects and are not required to inherit from the
45
+ * JavaScript Error class. In particular, GPUValidationError exposes a useful
46
+ * `message` while String(error) may only return "[object GPUValidationError]".
47
+ */
48
+ export function getWebGPUErrorMessage(error) {
49
+ if (error instanceof Error)
50
+ return error.message;
51
+ if (typeof error === 'string')
52
+ return error;
53
+ if (error && typeof error === 'object' && 'message' in error) {
54
+ const message = error.message;
55
+ if (typeof message === 'string' && message.length > 0)
56
+ return message;
57
+ }
58
+ return String(error);
59
+ }
60
+ function joinWebGPUErrors(...errors) {
61
+ return [
62
+ ...new Set(errors
63
+ .filter((error) => error != null)
64
+ .map(getWebGPUErrorMessage)
65
+ .filter(Boolean))
66
+ ].join('\n');
67
+ }
68
+ function shaderCompilationNow() {
69
+ return typeof performance === 'undefined' ? Date.now() : performance.now();
70
+ }
71
+ function shaderSourceLineCount(source) {
72
+ let lines = 1;
73
+ for (let offset = source.indexOf('\n'); offset >= 0; offset = source.indexOf('\n', offset + 1)) {
74
+ lines++;
75
+ }
76
+ return lines;
77
+ }
78
+ /** Fast diagnostic fingerprint; cache implementations use their own hashes. */
79
+ export function shaderSourceFingerprint(source) {
80
+ let hash = 0x811c9dc5;
81
+ for (let index = 0; index < source.length; index++) {
82
+ hash = Math.imul(hash ^ source.charCodeAt(index), 0x01000193);
83
+ }
84
+ return (hash >>> 0).toString(16).padStart(8, '0');
85
+ }
86
+ export function isWebGPUPipelineSpecializationFailure(...errors) {
87
+ return errors.some((error) => /(?:compute )?library failed creation/i.test(getWebGPUErrorMessage(error)));
88
+ }
89
+ /** 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(176);
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
+ };
124
+ // Fixed runtime trip bounds discourage thin-film loop unrolling. Keep 4 for
125
+ // 2 polarizations/substrates and 3 channels, independent of BSDF budgets.
126
+ RayTraceUniformViews.loopUnrollGuard[0] = 4;
127
+ RayTraceUniformViews.bevel[0] = 1;
128
+ RayTraceUniformViews.dispersionWavelengths[0] = 3;
129
+ return {
130
+ buffer: RayTraceUniformValues,
131
+ views: RayTraceUniformViews
132
+ };
133
+ }
134
+ export function packAnalyticsLightsToBuffer(lights, texturesIndices, selectionPmfs, iesProfileTexelOffsets = []) {
135
+ validateSceneLights(lights);
136
+ const numLights = lights.length;
137
+ if (numLights > FIXED_LIGHT_COUNT) {
138
+ console.warn(`Too many lights, only the first ${FIXED_LIGHT_COUNT} lights will be used.`);
139
+ }
140
+ const data = new Float32Array(numLights * PIXELS_PER_LIGHT * 4);
141
+ function packVec3(idx, v) {
142
+ for (let i = 0; i < 3; i++) {
143
+ data[i + idx * 4] = v[i];
144
+ }
145
+ }
146
+ const lightTypeConstants = {
147
+ DirectionalLight: CONSTANT_DIRECTIONAL_LIGHT,
148
+ SphereLight: CONSTANT_SPHERE_LIGHT,
149
+ RectLight: CONSTANT_RECT_LIGHT,
150
+ SpotLight: CONSTANT_SPOT_LIGHT
151
+ };
152
+ for (let i = 0; i < numLights; i++) {
153
+ const light = lights[i];
154
+ const color = light.color;
155
+ const intensity = light.intensity;
156
+ const off = i * PIXELS_PER_LIGHT;
157
+ packVec3(off, light.position);
158
+ data[off * 4 + 3] = lightTypeConstants[light.type];
159
+ // emission
160
+ const emissionScale = light.type === 'SpotLight' ? getSpotLightRadiance(light) : intensity;
161
+ packVec3(off + 1, [
162
+ color[0] * emissionScale,
163
+ color[1] * emissionScale,
164
+ color[2] * emissionScale
165
+ ]);
166
+ if (light.type === 'RectLight') {
167
+ data[(off + 1) * 4 + 3] = getRectLightArea(light);
168
+ packVec3(off + 2, getRectLightUOrV(light, 0));
169
+ data[(off + 2) * 4 + 3] = light.spread;
170
+ packVec3(off + 3, getRectLightUOrV(light, 1));
171
+ }
172
+ else if (light.type === 'SphereLight') {
173
+ data[(off + 1) * 4 + 3] = getSphereLightArea(light);
174
+ data[(off + 2) * 4 + 3] = light.radius;
175
+ }
176
+ else if (light.type === 'SpotLight') {
177
+ data[(off + 1) * 4 + 3] = getSpotLightArea(light);
178
+ packVec3(off + 2, getLightDirection(light));
179
+ data[(off + 2) * 4 + 3] = light.radius;
180
+ packVec3(off + 3, [
181
+ Math.cos((light.outerAngle * Math.PI) / 180),
182
+ Math.min(1, Math.max(0, 1 - light.innerAngle / light.outerAngle)),
183
+ 0
184
+ ]);
185
+ }
186
+ else if (light.type === 'DirectionalLight') {
187
+ packVec3(off + 2, getLightDirection(light));
188
+ }
189
+ data[(off + 3) * 4 + 3] = light.type === 'SpotLight'
190
+ ? iesProfileTexelOffsets[i] ?? -1
191
+ : texturesIndices[i];
192
+ data[(off + 4) * 4] = selectionPmfs[i] ?? 0;
193
+ packVec3(off + 5, getRigidLightFrame(light).x);
194
+ }
195
+ return data;
196
+ }
197
+ class WGPURayTracePass {
198
+ _device;
199
+ _rayTracePipeline;
200
+ _targetAspect = 1;
201
+ _width = 0;
202
+ _height = 0;
203
+ _meshes = [];
204
+ _sceneEnvironment = new SceneEnvironmentCache();
205
+ _rectLightTextures = new RectLightTextureTracker();
206
+ _analyticLights = [];
207
+ _iesProfileTexelOffsets = new Int32Array(0);
208
+ _materialChanges = new MaterialChangeTracker();
209
+ _pendingMaterialUpdate;
210
+ _materialData;
211
+ _maxTextureArrayBytes = DEFAULT_MAX_TEXTURE_ARRAY_BYTES;
212
+ _pendingGeometryUpdates = new Set();
213
+ _needsUpdateMeshData = true;
214
+ _needsUpdateMeshMembership = false;
215
+ _needsRefitTLAS = false;
216
+ _preparedMeshData;
217
+ _membershipCache = new MeshMembershipCache();
218
+ _pendingMembershipData;
219
+ _residentPreparedMeshData;
220
+ _tlas;
221
+ _meshSnapshot = [];
222
+ _snapshotByMesh = new WeakMap();
223
+ _residentSnapshotByMesh = new WeakMap();
224
+ _dirtyTransformMeshes = new Set();
225
+ _dirtyColorMeshes = new Set();
226
+ _geometriesNeedRefit = new Set();
227
+ _needsUpdateAnalyticLightsBuffer = true;
228
+ _activeTarget = {};
229
+ _targetOutputs = new Map();
230
+ releaseTarget(target) {
231
+ const output = this._targetOutputs.get(target);
232
+ if (!output)
233
+ return;
234
+ this._targetOutputs.delete(target);
235
+ for (const buffer of output.buffers)
236
+ buffer.destroy();
237
+ if (this._activeTarget === target) {
238
+ this._rayTraceBindGroups = undefined;
239
+ this._needsUpdateBindGroups = true;
240
+ }
241
+ }
242
+ getTargetMemoryBytes(target) {
243
+ return this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0;
244
+ }
245
+ useTarget(target) {
246
+ this._activeTarget = target;
247
+ const output = this._targetOutputs.get(target);
248
+ [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] = output?.buffers ?? [];
249
+ if (output?.bindGroup && output.pipeline === this._rayTracePipeline && this._rayTraceBindGroups) {
250
+ this._rayTraceBindGroups[3] = output.bindGroup;
251
+ }
252
+ else {
253
+ this._needsUpdateBindGroups = true;
254
+ }
255
+ }
256
+ _outColorBuffer;
257
+ _outNormalBuffer;
258
+ _outAlbedoBuffer;
259
+ _outShadowCatcherBuffer;
260
+ _uniforms;
261
+ _rtUniform = makeRayTraceUniform();
262
+ _mapInfoUniform;
263
+ _attributeData;
264
+ _attributeStride = 0;
265
+ _attributeOffsets = [];
266
+ _attrBuffers;
267
+ _materialsBuffer;
268
+ _lightSamplingTexture;
269
+ _lightSamplingTextureSize = [0, 0];
270
+ _lightDomainDiagnostics = {
271
+ environment: { count: 0, weight: 0, pmf: 0 },
272
+ infinite: { count: 0, weight: 0, pmf: 0 },
273
+ localAndEmissive: { count: 0, weight: 0, pmf: 0 }
274
+ };
275
+ _openPbrEnergyLut;
276
+ _bvhBuffer;
277
+ _instancesBuffer;
278
+ _emissiveCache = new EmissiveTriangleCache();
279
+ _lightChoices = new LightChoiceBuffer();
280
+ _lightSamplingData;
281
+ _lightSamplingScratch;
282
+ _lightSamplingTriangleCount = -1;
283
+ _emissiveTriangles = [];
284
+ _analyticLightSelectionPmfs = [];
285
+ _needsUpdateLightSamplingData = true;
286
+ _adaptiveSamplingTexture;
287
+ _adaptiveSamplingTextureSize = [1, 1];
288
+ _offlineAdaptivePipeline;
289
+ _offlineAdaptiveHistory;
290
+ _mapsSampler;
291
+ _maps;
292
+ _envImageSource;
293
+ _envTex;
294
+ _envDistributionTex;
295
+ _backgroundImageSource;
296
+ _backgroundTex;
297
+ _rayTracePipelineLayout;
298
+ _rayTraceBindGroups;
299
+ _blasLayoutMask = 1;
300
+ _compiledBlasLayoutMask = 0;
301
+ _compiledAnisotropyDirections = false;
302
+ _compiledCoatNormals = false;
303
+ _compiledHasAlpha = true;
304
+ _compiledSceneHasAlpha = true;
305
+ _alphaBlendStrategy = 'stochastic';
306
+ _constants = {
307
+ USE_BACKGROUND: false,
308
+ USE_BACKGROUND_MAP: false,
309
+ USE_GROUNDED_BACKGROUND: false,
310
+ SAMPLING_PATTERN: 0,
311
+ BEVEL_SAMPLES: DEFAULT_BEVEL_SAMPLES
312
+ };
313
+ _prevPipelineConstants = {};
314
+ _prevDefinesCode = '';
315
+ _hasTransmission = false;
316
+ _hasShadowCatcher = false;
317
+ _hasDisplacement = false;
318
+ // User set params
319
+ _bounces = -1;
320
+ _bevelSamples = DEFAULT_BEVEL_SAMPLES;
321
+ _directLightCandidates = DEFAULT_DIRECT_LIGHT_CANDIDATES;
322
+ _clamping;
323
+ _transmissiveAlphaMin = 1;
324
+ _viewProjInv = mat4.create();
325
+ _cameraWorld = mat4.create();
326
+ _apertureRadius = 0;
327
+ _focusDistance = 10;
328
+ _needsUpdateBindGroups = true;
329
+ _computeShaderModule;
330
+ _depthCompute;
331
+ _depthTracePipeline;
332
+ _depthPipeline;
333
+ _shaderCache;
334
+ _debugBVHBuffer;
335
+ _isCompilingShader = false;
336
+ _shaderCompilationDiagnostics;
337
+ _pipelineError;
338
+ // Fold scene invariants before backend inlining; runtime edits use uniforms.
339
+ _bakePipelineConstants = true;
340
+ _failedPipelineSignature;
341
+ constructor(device) {
342
+ this._device = device;
343
+ this._shaderCache = new WGPUShaderCache(device);
344
+ this._uniforms = {
345
+ cameraUniform: this._uniformBuffer(36 * 4),
346
+ rtUniform: this._uniformBuffer(this._rtUniform.buffer.byteLength),
347
+ analyticLights: this._uniformBuffer(FIXED_LIGHT_COUNT * PIXELS_PER_LIGHT * 4 * 4)
348
+ };
349
+ this._mapInfoUniform = this._uniformBuffer(4 * 4);
350
+ this._openPbrEnergyLut = device.createTexture({
351
+ format: 'r32float',
352
+ label: 'OpenPBR opaque dielectric energy compensation',
353
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
354
+ size: {
355
+ width: OPENPBR_ENERGY_LUT_WIDTH,
356
+ height: OPENPBR_ENERGY_LUT_HEIGHT
357
+ }
358
+ });
359
+ device.queue.writeTexture({ texture: this._openPbrEnergyLut }, getOpenPbrEnergyLutData(), { bytesPerRow: OPENPBR_ENERGY_LUT_WIDTH * Float32Array.BYTES_PER_ELEMENT }, {
360
+ width: OPENPBR_ENERGY_LUT_WIDTH,
361
+ height: OPENPBR_ENERGY_LUT_HEIGHT
362
+ });
363
+ this._mapsSampler = device.createSampler({
364
+ minFilter: 'linear',
365
+ magFilter: 'linear',
366
+ addressModeU: 'clamp-to-edge',
367
+ addressModeV: 'clamp-to-edge'
368
+ });
369
+ this._setBackgroundMap(undefined);
370
+ this._adaptiveSamplingTexture = device.createTexture({
371
+ format: 'r32float',
372
+ label: 'Adaptive sample targets placeholder',
373
+ usage: GPUTextureUsage.TEXTURE_BINDING |
374
+ GPUTextureUsage.COPY_DST |
375
+ GPUTextureUsage.STORAGE_BINDING,
376
+ size: [1, 1]
377
+ });
378
+ device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, new Float32Array([Number.MAX_SAFE_INTEGER]), { bytesPerRow: 4 }, [1, 1]);
379
+ }
380
+ getOutputBuffer() {
381
+ return {
382
+ color: this._outColorBuffer,
383
+ normal: this._outNormalBuffer,
384
+ albedo: this._outAlbedoBuffer,
385
+ shadowCatcher: this._outShadowCatcherBuffer
386
+ };
387
+ }
388
+ _ensureAdaptiveSamplingTexture(width, height) {
389
+ if (this._adaptiveSamplingTextureSize[0] === width &&
390
+ this._adaptiveSamplingTextureSize[1] === height) {
391
+ return;
392
+ }
393
+ this._adaptiveSamplingTexture.destroy();
394
+ this._adaptiveSamplingTexture = this._device.createTexture({
395
+ format: 'r32float',
396
+ label: 'Adaptive sample targets',
397
+ usage: GPUTextureUsage.TEXTURE_BINDING |
398
+ GPUTextureUsage.COPY_DST |
399
+ GPUTextureUsage.STORAGE_BINDING,
400
+ size: [width, height]
401
+ });
402
+ this._adaptiveSamplingTextureSize = [width, height];
403
+ this._needsUpdateBindGroups = true;
404
+ }
405
+ /**
406
+ * Reduces pilot moments and expands final sample targets without transferring
407
+ * full-resolution buffers through JavaScript. Only compact per-tile history
408
+ * is read back for diagnostics and animation-frame reuse.
409
+ */
410
+ async prepareOfflineAdaptiveSampling(options) {
411
+ const width = this._width;
412
+ const height = this._height;
413
+ if (!width || !height || !this._outColorBuffer || !this._outNormalBuffer) {
414
+ return undefined;
415
+ }
416
+ const normalized = normalizeOfflineAdaptiveSamplingOptions(options, width, height);
417
+ const { maxSamples, minSamples, tileSize, columns, rows, absoluteError, relativeError, confidence, previous } = normalized;
418
+ const tileCount = columns * rows;
419
+ const tileByteLength = tileCount * Float32Array.BYTES_PER_ELEMENT;
420
+ const oldResidentHistory = this._offlineAdaptiveHistory;
421
+ let ownsPreviousBuffer = false;
422
+ let previousBuffer;
423
+ if (previous && oldResidentHistory?.history === previous) {
424
+ previousBuffer = oldResidentHistory.varianceBuffer;
425
+ }
426
+ else {
427
+ previousBuffer = this._device.createBuffer({
428
+ label: previous
429
+ ? 'Offline previous tile variance upload'
430
+ : 'Offline empty tile variance',
431
+ size: previous ? tileByteLength : Float32Array.BYTES_PER_ELEMENT,
432
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
433
+ });
434
+ this._device.queue.writeBuffer(previousBuffer, 0, previous?.variance ?? new Float32Array(1));
435
+ ownsPreviousBuffer = true;
436
+ }
437
+ const uniformBuffer = this._device.createBuffer({
438
+ label: 'Offline adaptive sampling parameters',
439
+ size: 48,
440
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
441
+ });
442
+ const uniformData = new ArrayBuffer(48);
443
+ new Uint32Array(uniformData, 0, 8).set([
444
+ width,
445
+ height,
446
+ tileSize,
447
+ columns,
448
+ rows,
449
+ maxSamples,
450
+ minSamples,
451
+ previous ? 1 : 0
452
+ ]);
453
+ new Float32Array(uniformData, 32, 4).set([
454
+ absoluteError,
455
+ relativeError,
456
+ confidence,
457
+ 0
458
+ ]);
459
+ this._device.queue.writeBuffer(uniformBuffer, 0, uniformData);
460
+ const meanBuffer = this._device.createBuffer({
461
+ label: 'Offline tile mean luminance',
462
+ size: tileByteLength,
463
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
464
+ });
465
+ const varianceBuffer = this._device.createBuffer({
466
+ label: 'Offline current tile variance',
467
+ size: tileByteLength,
468
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
469
+ });
470
+ const targetBuffer = this._device.createBuffer({
471
+ label: 'Offline tile sample targets',
472
+ size: tileByteLength,
473
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
474
+ });
475
+ const readbackBuffer = this._device.createBuffer({
476
+ label: 'Offline compact tile history readback',
477
+ size: tileByteLength * 3,
478
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
479
+ });
480
+ this._ensureAdaptiveSamplingTexture(width, height);
481
+ this._offlineAdaptivePipeline ??=
482
+ await this._device.createComputePipelineAsync({
483
+ label: 'Offline adaptive sampling planner',
484
+ layout: 'auto',
485
+ compute: {
486
+ module: this._device.createShaderModule({
487
+ label: 'Offline adaptive sampling planner',
488
+ code: offlineAdaptiveCompute
489
+ }),
490
+ entryPoint: 'main'
491
+ }
492
+ });
493
+ const bindGroup = this._device.createBindGroup({
494
+ label: 'Offline adaptive sampling planner',
495
+ layout: this._offlineAdaptivePipeline.getBindGroupLayout(0),
496
+ entries: [
497
+ { binding: 0, resource: { buffer: uniformBuffer } },
498
+ { binding: 1, resource: { buffer: this._outColorBuffer } },
499
+ { binding: 2, resource: { buffer: this._outNormalBuffer } },
500
+ { binding: 3, resource: { buffer: previousBuffer } },
501
+ { binding: 4, resource: { buffer: meanBuffer } },
502
+ { binding: 5, resource: { buffer: varianceBuffer } },
503
+ { binding: 6, resource: { buffer: targetBuffer } },
504
+ {
505
+ binding: 7,
506
+ resource: this._adaptiveSamplingTexture.createView()
507
+ }
508
+ ]
509
+ });
510
+ const encoder = this._device.createCommandEncoder({
511
+ label: 'Offline adaptive sampling planner'
512
+ });
513
+ const pass = encoder.beginComputePass({
514
+ label: 'Offline adaptive sampling planner'
515
+ });
516
+ pass.setPipeline(this._offlineAdaptivePipeline);
517
+ pass.setBindGroup(0, bindGroup);
518
+ pass.dispatchWorkgroups(Math.ceil(tileCount / 64));
519
+ pass.end();
520
+ encoder.copyBufferToBuffer(meanBuffer, 0, readbackBuffer, 0, tileByteLength);
521
+ encoder.copyBufferToBuffer(varianceBuffer, 0, readbackBuffer, tileByteLength, tileByteLength);
522
+ encoder.copyBufferToBuffer(targetBuffer, 0, readbackBuffer, tileByteLength * 2, tileByteLength);
523
+ this._device.queue.submit([encoder.finish()]);
524
+ let succeeded = false;
525
+ try {
526
+ await readbackBuffer.mapAsync(GPUMapMode.READ);
527
+ const mapped = readbackBuffer.getMappedRange();
528
+ const meanLuminance = new Float32Array(mapped, 0, tileCount).slice();
529
+ const variance = new Float32Array(mapped, tileByteLength, tileCount).slice();
530
+ const sampleTargets = new Uint32Array(mapped, tileByteLength * 2, tileCount).slice();
531
+ const history = {
532
+ width,
533
+ height,
534
+ tileSize,
535
+ columns,
536
+ rows,
537
+ meanLuminance,
538
+ variance,
539
+ sampleTargets
540
+ };
541
+ let maximumTarget = minSamples;
542
+ let totalTargetSamples = 0;
543
+ for (let tile = 0; tile < tileCount; tile++) {
544
+ const target = sampleTargets[tile];
545
+ maximumTarget = Math.max(maximumTarget, target);
546
+ const tileX = tile % columns;
547
+ const tileY = Math.floor(tile / columns);
548
+ const tileWidth = Math.min(tileSize, width - tileX * tileSize);
549
+ const tileHeight = Math.min(tileSize, height - tileY * tileSize);
550
+ totalTargetSamples += target * tileWidth * tileHeight;
551
+ }
552
+ this._offlineAdaptiveHistory = { history, varianceBuffer };
553
+ this._rtUniform.views.adaptiveSampling[0] = 1;
554
+ succeeded = true;
555
+ if (oldResidentHistory?.varianceBuffer !== varianceBuffer) {
556
+ oldResidentHistory?.varianceBuffer.destroy();
557
+ }
558
+ return {
559
+ maximumTarget,
560
+ averageTarget: totalTargetSamples / (width * height),
561
+ history
562
+ };
563
+ }
564
+ finally {
565
+ if (readbackBuffer.mapState === 'mapped')
566
+ readbackBuffer.unmap();
567
+ readbackBuffer.destroy();
568
+ meanBuffer.destroy();
569
+ targetBuffer.destroy();
570
+ uniformBuffer.destroy();
571
+ if (ownsPreviousBuffer)
572
+ previousBuffer.destroy();
573
+ if (!succeeded)
574
+ varianceBuffer.destroy();
575
+ }
576
+ }
577
+ _createPipelineLayout() {
578
+ //Bind group layouts
579
+ const commonBufferLayoutEntries = Array.from({ length: 4 }, (_, i) => ({
580
+ binding: i,
581
+ visibility: GPUShaderStage.COMPUTE,
582
+ buffer: {
583
+ type: 'read-only-storage'
584
+ }
585
+ }));
586
+ const buffersBindGroupLayout = this._shaderCache.createBindGroupLayout({
587
+ label: 'Buffers',
588
+ entries: commonBufferLayoutEntries
589
+ });
590
+ const textureLayoutEntries = [
591
+ {
592
+ binding: 0,
593
+ visibility: GPUShaderStage.COMPUTE,
594
+ sampler: {}
595
+ },
596
+ {
597
+ binding: 1,
598
+ visibility: GPUShaderStage.COMPUTE,
599
+ texture: {
600
+ viewDimension: '2d-array'
601
+ }
602
+ },
603
+ // Map size
604
+ {
605
+ binding: 3,
606
+ visibility: GPUShaderStage.COMPUTE,
607
+ buffer: {
608
+ type: 'uniform'
609
+ }
610
+ },
611
+ // Env map
612
+ {
613
+ binding: 4,
614
+ visibility: GPUShaderStage.COMPUTE,
615
+ texture: {
616
+ viewDimension: '2d',
617
+ sampleType: 'unfilterable-float'
618
+ }
619
+ },
620
+ // Env map distribution
621
+ {
622
+ binding: 5,
623
+ visibility: GPUShaderStage.COMPUTE,
624
+ texture: {
625
+ viewDimension: '2d',
626
+ sampleType: 'unfilterable-float'
627
+ }
628
+ },
629
+ // Background map
630
+ {
631
+ binding: 6,
632
+ visibility: GPUShaderStage.COMPUTE,
633
+ texture: {
634
+ viewDimension: '2d',
635
+ sampleType: 'unfilterable-float'
636
+ }
637
+ },
638
+ {
639
+ binding: 8,
640
+ visibility: GPUShaderStage.COMPUTE,
641
+ texture: {
642
+ viewDimension: '2d',
643
+ sampleType: 'unfilterable-float'
644
+ }
645
+ },
646
+ {
647
+ binding: 9,
648
+ visibility: GPUShaderStage.COMPUTE,
649
+ texture: {
650
+ viewDimension: '2d',
651
+ sampleType: 'unfilterable-float'
652
+ }
653
+ },
654
+ {
655
+ binding: 10,
656
+ visibility: GPUShaderStage.COMPUTE,
657
+ texture: {
658
+ viewDimension: '2d',
659
+ sampleType: 'unfilterable-float'
660
+ }
661
+ }
662
+ ];
663
+ const texturesBindGroupLayout = this._shaderCache.createBindGroupLayout({
664
+ label: 'Textures', entries: textureLayoutEntries
665
+ });
666
+ const uniformsBindGroupLayout = this._shaderCache.createBindGroupLayout({
667
+ label: 'Uniforms',
668
+ entries: [
669
+ ...Array.from({ length: Object.keys(this._uniforms).length }, (_, i) => ({
670
+ binding: i,
671
+ visibility: GPUShaderStage.COMPUTE,
672
+ buffer: {
673
+ type: 'uniform'
674
+ }
675
+ })),
676
+ ...([])
677
+ ]
678
+ });
679
+ const rayTraceOutBuffersBindGroupLayout = this._shaderCache.createBindGroupLayout({
680
+ label: 'Ray Trace Out Buffers',
681
+ entries: [
682
+ ...Array.from({ length: 4 }, (_, i) => ({
683
+ binding: i,
684
+ visibility: GPUShaderStage.COMPUTE,
685
+ buffer: { type: 'storage' }
686
+ })),
687
+ ...([])
688
+ ]
689
+ });
690
+ this._rayTracePipelineLayout = this._shaderCache.createPipelineLayout({
691
+ label: 'Ray Trace Pipeline Layout',
692
+ bindGroupLayouts: [
693
+ buffersBindGroupLayout,
694
+ texturesBindGroupLayout,
695
+ uniformsBindGroupLayout,
696
+ rayTraceOutBuffersBindGroupLayout
697
+ ]
698
+ });
699
+ }
700
+ _updateGPUBuffer(label, gpuBuffer, data, minSize = 200) {
701
+ const device = this._device;
702
+ const size = Math.max(data.byteLength, minSize);
703
+ if (gpuBuffer == null || gpuBuffer.size < size) {
704
+ this._needsUpdateBindGroups = true;
705
+ if (gpuBuffer) {
706
+ gpuBuffer.destroy();
707
+ }
708
+ gpuBuffer = device.createBuffer({
709
+ label,
710
+ size,
711
+ usage: GPUBufferUsage.STORAGE |
712
+ GPUBufferUsage.COPY_DST |
713
+ GPUBufferUsage.COPY_SRC
714
+ });
715
+ }
716
+ this._device.queue.writeBuffer(gpuBuffer, 0, data.buffer);
717
+ return gpuBuffer;
718
+ }
719
+ _uniformBuffer(size) {
720
+ this._needsUpdateBindGroups = true;
721
+ return this._device.createBuffer({
722
+ size,
723
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
724
+ });
725
+ }
726
+ async updatePipeline() {
727
+ if (this._isCompilingShader) {
728
+ return;
729
+ }
730
+ // Set this before any synchronous preparation. updatePipeline is called
731
+ // without awaiting it, so a preparation error must not let createPass run
732
+ // with an undefined pipeline in the same frame.
733
+ this._isCompilingShader = true;
734
+ let attemptedPipelineSignature;
735
+ let compilationStartedAt;
736
+ let pipelineCompilationStartedAt;
737
+ try {
738
+ if (this._needsUpdateMeshData) {
739
+ this._updateMeshData();
740
+ }
741
+ else if (this._needsUpdateMeshMembership) {
742
+ this._updateMeshMembership();
743
+ }
744
+ if (this._pendingGeometryUpdates.size)
745
+ this._updateGeometryData();
746
+ if (this._needsRefitTLAS)
747
+ this._refitMeshInstances();
748
+ if (this._dirtyColorMeshes.size)
749
+ this._updateMeshColors();
750
+ if (this._pendingMaterialUpdate)
751
+ this._updateMaterialData();
752
+ if (this._needsUpdateLightSamplingData) {
753
+ this._updateLightSamplingData();
754
+ }
755
+ const device = this._device;
756
+ const constants = this._constants;
757
+ this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(constants);
758
+ const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) &&
759
+ this._alphaBlendStrategy === 'deterministic-source-over';
760
+ const hasSceneAlpha = Boolean(constants.HAS_ALPHA);
761
+ const pipelineSignature = JSON.stringify({
762
+ constants: Object.keys(constants)
763
+ .sort()
764
+ .map((key) => [key, constants[key]]),
765
+ hasDisplacement: this._hasDisplacement,
766
+ hasAlpha,
767
+ hasSceneAlpha,
768
+ hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
769
+ hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
770
+ blasLayoutMask: this._blasLayoutMask,
771
+ bakePipelineConstants: this._bakePipelineConstants
772
+ });
773
+ attemptedPipelineSignature = pipelineSignature;
774
+ if (pipelineSignature === this._failedPipelineSignature) {
775
+ return;
776
+ }
777
+ this._pipelineError = undefined;
778
+ // TODO hardcoded defines that will be replaced in the code.
779
+ const definesKeys = [
780
+ 'NUM_MAPS',
781
+ 'TLAS_STACK_SIZE',
782
+ 'BLAS_STACK_SIZE',
783
+ 'NUM_UNIFORM_LIGHTS'
784
+ ];
785
+ // Scene feature flags and background selection are runtime uniforms so
786
+ // interactive edits do not recompile the path tracer.
787
+ const constantsNotUsed = [
788
+ ...Object.keys(WGPU_SCENE_FEATURE_BITS),
789
+ 'USE_BACKGROUND',
790
+ 'USE_BACKGROUND_MAP',
791
+ 'USE_GROUNDED_BACKGROUND',
792
+ ...MATERIAL_TEXTURE_DEFINES,
793
+ 'USE_ANISOTROPY_DIRECTIONS',
794
+ 'USE_COAT_NORMAL_FRAMES',
795
+ 'COLOR_OFF',
796
+ 'USE_VERTEX_COLOR',
797
+ 'HAS_ALPHA_BLEND',
798
+ 'HAS_ALPHA_MASK'
799
+ ];
800
+ const defines = definesKeys.reduce((obj, key) => {
801
+ obj[key] = constants[key];
802
+ return obj;
803
+ }, {});
804
+ const pipelineConstantsKeys = Object.keys(constants)
805
+ .sort()
806
+ .filter((key) => !definesKeys.includes(key) && !constantsNotUsed.includes(key));
807
+ const defineCode = definesKeys
808
+ .map((key) => `const ${key} = ${defines[key]};`)
809
+ .join('\n');
810
+ const previousPipelineConstantsKeys = Object.keys(this._prevPipelineConstants)
811
+ .filter((key) => !definesKeys.includes(key) && !constantsNotUsed.includes(key))
812
+ .sort();
813
+ const needsCreateShaderModule = !this._rayTracePipelineLayout ||
814
+ !this._computeShaderModule ||
815
+ pipelineConstantsKeys.join(',') !==
816
+ previousPipelineConstantsKeys.join(',') ||
817
+ defineCode !== this._prevDefinesCode ||
818
+ hasAlpha !== this._compiledHasAlpha ||
819
+ hasSceneAlpha !== this._compiledSceneHasAlpha ||
820
+ Boolean(constants.USE_ANISOTROPY_DIRECTIONS) !== this._compiledAnisotropyDirections ||
821
+ Boolean(constants.USE_COAT_NORMAL_FRAMES) !== this._compiledCoatNormals ||
822
+ this._blasLayoutMask !== this._compiledBlasLayoutMask ||
823
+ (this._bakePipelineConstants &&
824
+ pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]));
825
+ const needsCreatePipeline = needsCreateShaderModule ||
826
+ pipelineConstantsKeys.some((key) => constants[key] !== this._prevPipelineConstants[key]);
827
+ compilationStartedAt = needsCreatePipeline
828
+ ? shaderCompilationNow()
829
+ : undefined;
830
+ let sourceGenerationTimeMs = 0;
831
+ let moduleCreationTimeMs = 0;
832
+ let sourceBytes = this._shaderCompilationDiagnostics?.sourceBytes ?? 0;
833
+ let sourceLines = this._shaderCompilationDiagnostics?.sourceLines ?? 0;
834
+ let sourceFingerprint = this._shaderCompilationDiagnostics?.modules[0]?.sourceFingerprint ?? '';
835
+ if (needsCreateShaderModule) {
836
+ const sourceGenerationStartedAt = shaderCompilationNow();
837
+ const constantsCode = pipelineConstantsKeys
838
+ .map((key) => `${this._bakePipelineConstants ? 'const' : 'override'} ${key} = ${constants[key].toString()};`)
839
+ .join('\n');
840
+ const rayTraceShaderCode = /* wgsl */ `
841
+ ${defineCode}
842
+ ${constantsCode}
843
+ ${rayTraceCompute({
844
+ hasDisplacement: this._hasDisplacement,
845
+ hasAlpha,
846
+ hasSceneAlpha,
847
+ hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
848
+ hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
849
+ blasLayoutMask: this._blasLayoutMask
850
+ })}`;
851
+ sourceBytes = rayTraceShaderCode.length;
852
+ sourceLines = shaderSourceLineCount(rayTraceShaderCode);
853
+ sourceFingerprint = shaderSourceFingerprint(rayTraceShaderCode);
854
+ sourceGenerationTimeMs =
855
+ shaderCompilationNow() - sourceGenerationStartedAt;
856
+ const moduleCreationStartedAt = shaderCompilationNow();
857
+ this._computeShaderModule = this._shaderCache.createShaderModule({
858
+ label: 'Ray Trace',
859
+ code: rayTraceShaderCode
860
+ });
861
+ moduleCreationTimeMs =
862
+ shaderCompilationNow() - moduleCreationStartedAt;
863
+ this._compiledHasAlpha = hasAlpha;
864
+ this._compiledSceneHasAlpha = hasSceneAlpha;
865
+ this._compiledAnisotropyDirections = Boolean(constants.USE_ANISOTROPY_DIRECTIONS);
866
+ this._compiledCoatNormals = Boolean(constants.USE_COAT_NORMAL_FRAMES);
867
+ this._compiledBlasLayoutMask = this._blasLayoutMask;
868
+ }
869
+ if (needsCreatePipeline) {
870
+ if (!this._rayTracePipelineLayout) {
871
+ device.pushErrorScope('validation');
872
+ let layoutScopeOpen = true;
873
+ try {
874
+ this._createPipelineLayout();
875
+ const layoutError = await device.popErrorScope();
876
+ layoutScopeOpen = false;
877
+ if (layoutError)
878
+ throw layoutError;
879
+ }
880
+ catch (error) {
881
+ const scopedError = layoutScopeOpen
882
+ ? await device.popErrorScope().catch(() => null)
883
+ : null;
884
+ this._rayTracePipelineLayout = undefined;
885
+ const detail = joinWebGPUErrors(error, scopedError);
886
+ throw new Error(`Unable to create the ray tracing pipeline layout: ${detail}`);
887
+ }
888
+ }
889
+ // NOTE must before creating pipeline.
890
+ // Because createComputePipelineAsync is async.
891
+ // The constants may be changed after the pipeline is created(in setMesh).
892
+ this._prevPipelineConstants = { ...this._constants };
893
+ this._prevDefinesCode = defineCode;
894
+ this._needsUpdateBindGroups = true;
895
+ device.pushErrorScope('validation');
896
+ let pipelineScopeOpen = true;
897
+ try {
898
+ const pipelineConstants = !this._bakePipelineConstants
899
+ ? pipelineConstantsKeys.reduce((obj, key) => {
900
+ obj[key] = +this._constants[key];
901
+ return obj;
902
+ }, {})
903
+ : undefined;
904
+ const diagnostic = {
905
+ label: 'Ray Trace',
906
+ entryPoint: 'main',
907
+ firstFrameRequired: true,
908
+ status: 'compiling'
909
+ };
910
+ pipelineCompilationStartedAt = shaderCompilationNow();
911
+ diagnostic.startedAtMs = 0;
912
+ diagnostic.concurrentCompilationCount = 1;
913
+ this._shaderCompilationDiagnostics = {
914
+ status: 'compiling',
915
+ sourceBytes,
916
+ sourceLines,
917
+ pipelineCount: 1,
918
+ completedPipelineCount: 0,
919
+ sourceGenerationTimeMs,
920
+ moduleCreationTimeMs,
921
+ modules: [
922
+ {
923
+ label: 'Ray Trace',
924
+ sourceBytes,
925
+ sourceLines,
926
+ sourceFingerprint
927
+ }
928
+ ],
929
+ pipelines: [{ ...diagnostic }],
930
+ maximumConcurrentPipelineCount: 1,
931
+ peakConcurrentPipelineCount: 1
932
+ };
933
+ let rayTracePipeline;
934
+ try {
935
+ rayTracePipeline = await this._shaderCache.createComputePipelineAsync({
936
+ label: 'Ray Trace',
937
+ layout: this._rayTracePipelineLayout,
938
+ compute: {
939
+ module: this._computeShaderModule,
940
+ entryPoint: 'main',
941
+ constants: pipelineConstants
942
+ }
943
+ });
944
+ diagnostic.status = 'ready';
945
+ }
946
+ catch (error) {
947
+ diagnostic.status = 'failed';
948
+ diagnostic.error = getWebGPUErrorMessage(error);
949
+ // Pipeline creation already reports WGSL failures. Pull the much
950
+ // heavier module diagnostics only on that failure path so normal
951
+ // startup does not schedule a second full-module inspection.
952
+ const info = await this._computeShaderModule
953
+ .getCompilationInfo()
954
+ .catch(() => undefined);
955
+ const shaderErrors = (info?.messages ?? [])
956
+ .filter((message) => message.type === 'error');
957
+ if (shaderErrors.length) {
958
+ throw new Error(shaderErrors
959
+ .map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
960
+ .join('\n'));
961
+ }
962
+ throw error;
963
+ }
964
+ finally {
965
+ diagnostic.endedAtMs =
966
+ shaderCompilationNow() - pipelineCompilationStartedAt;
967
+ diagnostic.durationMs = diagnostic.endedAtMs;
968
+ }
969
+ this._rayTracePipeline = rayTracePipeline;
970
+ this._depthCompute = { module: this._computeShaderModule, constants: pipelineConstants };
971
+ const pipelineError = await device.popErrorScope();
972
+ pipelineScopeOpen = false;
973
+ if (pipelineError)
974
+ throw pipelineError;
975
+ const compilationCompletedAt = shaderCompilationNow();
976
+ this._shaderCompilationDiagnostics = {
977
+ ...this._shaderCompilationDiagnostics,
978
+ status: 'ready',
979
+ completedPipelineCount: 1,
980
+ pipelines: [{ ...diagnostic }],
981
+ pipelineCompilationTimeMs: compilationCompletedAt - pipelineCompilationStartedAt,
982
+ totalTimeMs: compilationCompletedAt - compilationStartedAt
983
+ };
984
+ }
985
+ catch (error) {
986
+ const scopedError = pipelineScopeOpen
987
+ ? await device.popErrorScope().catch(() => null)
988
+ : null;
989
+ const detail = joinWebGPUErrors(error, scopedError);
990
+ if (!this._bakePipelineConstants &&
991
+ isWebGPUPipelineSpecializationFailure(error, scopedError)) {
992
+ const failedAt = shaderCompilationNow();
993
+ if (this._shaderCompilationDiagnostics?.status === 'compiling') {
994
+ this._shaderCompilationDiagnostics = {
995
+ ...this._shaderCompilationDiagnostics,
996
+ status: 'failed',
997
+ pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
998
+ ? undefined
999
+ : failedAt - pipelineCompilationStartedAt,
1000
+ totalTimeMs: compilationStartedAt == null
1001
+ ? undefined
1002
+ : failedAt - compilationStartedAt,
1003
+ error: detail
1004
+ };
1005
+ }
1006
+ // WebKit can accept a large WGSL module but fail while translating
1007
+ // its specialization-constant variant to Metal. Baking the values
1008
+ // into WGSL lets its front end eliminate disabled feature branches
1009
+ // before creating the Metal library.
1010
+ this._bakePipelineConstants = true;
1011
+ this._rayTracePipeline = undefined;
1012
+ this._rayTraceBindGroups = undefined;
1013
+ this._computeShaderModule = undefined;
1014
+ this._prevPipelineConstants = {};
1015
+ console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
1016
+ return;
1017
+ }
1018
+ throw new Error(detail);
1019
+ }
1020
+ }
1021
+ // TODO rebind when lights, materials, bvh changed.
1022
+ if (this._needsUpdateBindGroups) {
1023
+ this._needsUpdateBindGroups = false;
1024
+ this._updateBindGroups();
1025
+ }
1026
+ this._failedPipelineSignature = undefined;
1027
+ }
1028
+ catch (error) {
1029
+ this._pipelineError = error;
1030
+ const failedAt = shaderCompilationNow();
1031
+ if (this._shaderCompilationDiagnostics?.status === 'compiling') {
1032
+ this._shaderCompilationDiagnostics = {
1033
+ ...this._shaderCompilationDiagnostics,
1034
+ status: 'failed',
1035
+ pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
1036
+ ? undefined
1037
+ : failedAt - pipelineCompilationStartedAt,
1038
+ totalTimeMs: compilationStartedAt == null
1039
+ ? undefined
1040
+ : failedAt - compilationStartedAt,
1041
+ error: getWebGPUErrorMessage(error)
1042
+ };
1043
+ }
1044
+ this._failedPipelineSignature = attemptedPipelineSignature;
1045
+ // Do not allow the render loop to submit the previous/partial state.
1046
+ // The failed signature prevents retrying the same shader every frame;
1047
+ // changing a pipeline-affecting setting permits a new attempt.
1048
+ this._rayTracePipeline = undefined;
1049
+ this._rayTraceBindGroups = undefined;
1050
+ this._prevPipelineConstants = {};
1051
+ this._needsUpdateBindGroups = true;
1052
+ console.error('Failed to update WebGPU ray tracing pipeline.', error);
1053
+ }
1054
+ finally {
1055
+ this._isCompilingShader = false;
1056
+ }
1057
+ }
1058
+ async readDepthSamples(area, aspect) {
1059
+ const tracePipeline = this._rayTracePipeline;
1060
+ const groups = this._rayTraceBindGroups;
1061
+ if (!tracePipeline || !groups || !this._depthCompute || this._isCompilingShader)
1062
+ return;
1063
+ const packed = packDepthSampleArea(area, aspect ?? this._width / this._height);
1064
+ if (this._depthTracePipeline !== tracePipeline || !this._depthPipeline) {
1065
+ const outputLayout = this._shaderCache.createBindGroupLayout({
1066
+ label: 'Depth query output',
1067
+ entries: [
1068
+ { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
1069
+ { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } }
1070
+ ]
1071
+ });
1072
+ const layout = this._shaderCache.createPipelineLayout({
1073
+ label: 'Depth query layout',
1074
+ bindGroupLayouts: [0, 1, 2].map(i => tracePipeline.getBindGroupLayout(i)).concat(outputLayout)
1075
+ });
1076
+ this._depthTracePipeline = tracePipeline;
1077
+ this._depthPipeline = this._shaderCache.createComputePipelineAsync({
1078
+ label: 'Depth query', layout,
1079
+ compute: { ...this._depthCompute, entryPoint: 'depthQuery',
1080
+ constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1 } }
1081
+ });
1082
+ }
1083
+ const pipeline = await this._depthPipeline;
1084
+ // Scene uploads can replace the buffers while the small query compiles.
1085
+ if (tracePipeline !== this._rayTracePipeline || groups !== this._rayTraceBindGroups)
1086
+ return;
1087
+ const device = this._device;
1088
+ const bytes = DEPTH_SAMPLE_COUNT * 16;
1089
+ const output = device.createBuffer({ label: 'Depth samples', size: bytes,
1090
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
1091
+ const readback = device.createBuffer({ label: 'Depth readback', size: bytes,
1092
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
1093
+ const params = device.createBuffer({ label: 'Depth area', size: 16,
1094
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
1095
+ try {
1096
+ device.queue.writeBuffer(params, 0, packed);
1097
+ const outputGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(3), entries: [
1098
+ { binding: 0, resource: { buffer: output } },
1099
+ { binding: 4, resource: { buffer: params } }
1100
+ ] });
1101
+ const encoder = device.createCommandEncoder({ label: 'Depth query' });
1102
+ const pass = encoder.beginComputePass();
1103
+ pass.setPipeline(pipeline);
1104
+ for (let i = 0; i < 3; i++)
1105
+ pass.setBindGroup(i, groups[i]);
1106
+ pass.setBindGroup(3, outputGroup);
1107
+ pass.dispatchWorkgroups(1);
1108
+ pass.end();
1109
+ encoder.copyBufferToBuffer(output, 0, readback, 0, bytes);
1110
+ device.queue.submit([encoder.finish()]);
1111
+ await readback.mapAsync(GPUMapMode.READ);
1112
+ return unpackDepthSamples(new Float32Array(readback.getMappedRange()));
1113
+ }
1114
+ finally {
1115
+ if (readback.mapState === 'mapped')
1116
+ readback.unmap();
1117
+ readback.destroy();
1118
+ output.destroy();
1119
+ params.destroy();
1120
+ }
1121
+ }
1122
+ isCompilingShader() {
1123
+ return this._isCompilingShader;
1124
+ }
1125
+ isPipelineReady() {
1126
+ return ((!!this._rayTracePipeline) &&
1127
+ !!this._rayTraceBindGroups);
1128
+ }
1129
+ getPipelineError() {
1130
+ return this._pipelineError;
1131
+ }
1132
+ _updateBindGroups() {
1133
+ const { _device } = this;
1134
+ const bindGroupPipeline = this._rayTracePipeline;
1135
+ if (!bindGroupPipeline) {
1136
+ throw new Error('WebGPU ray tracing bind-group pipeline is not ready.');
1137
+ }
1138
+ if (!this._bvhBuffer || !this._instancesBuffer) {
1139
+ throw new Error('WebGPU BVH buffers are not ready.');
1140
+ }
1141
+ const bufferBindGroup = _device.createBindGroup({
1142
+ layout: bindGroupPipeline.getBindGroupLayout(0),
1143
+ entries: [
1144
+ this._bvhBuffer,
1145
+ this._instancesBuffer,
1146
+ this._attrBuffers,
1147
+ this._materialsBuffer
1148
+ ].map((buffer, i) => ({
1149
+ binding: i,
1150
+ resource: {
1151
+ buffer: buffer
1152
+ }
1153
+ }))
1154
+ });
1155
+ const texturesBindGroup = _device.createBindGroup({
1156
+ layout: bindGroupPipeline.getBindGroupLayout(1),
1157
+ entries: [
1158
+ {
1159
+ binding: 0,
1160
+ resource: this._mapsSampler
1161
+ },
1162
+ {
1163
+ binding: 1,
1164
+ resource: this._maps.createView()
1165
+ },
1166
+ {
1167
+ binding: 3,
1168
+ resource: {
1169
+ buffer: this._mapInfoUniform
1170
+ }
1171
+ },
1172
+ { binding: 4, resource: this._envTex.createView() },
1173
+ { binding: 5, resource: this._envDistributionTex.createView() },
1174
+ { binding: 6, resource: this._backgroundTex.createView() },
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
+ });
1183
+ const uniformBindGroup = _device.createBindGroup({
1184
+ layout: bindGroupPipeline.getBindGroupLayout(2),
1185
+ entries: Object.keys(this._uniforms).map((key, i) => ({
1186
+ binding: i,
1187
+ resource: {
1188
+ buffer: this._uniforms[key]
1189
+ }
1190
+ }))
1191
+ });
1192
+ const target = this._targetOutputs.get(this._activeTarget);
1193
+ const outputBindGroup = target.pipeline === bindGroupPipeline && target.bindGroup || _device.createBindGroup({
1194
+ layout: bindGroupPipeline.getBindGroupLayout(3),
1195
+ entries: [
1196
+ this._outColorBuffer,
1197
+ this._outNormalBuffer,
1198
+ this._outAlbedoBuffer,
1199
+ this._outShadowCatcherBuffer
1200
+ ].map((buffer, i) => ({
1201
+ binding: i,
1202
+ resource: { buffer }
1203
+ }))
1204
+ });
1205
+ target.bindGroup = outputBindGroup;
1206
+ target.pipeline = bindGroupPipeline;
1207
+ this._rayTraceBindGroups = [
1208
+ bufferBindGroup,
1209
+ texturesBindGroup,
1210
+ uniformBindGroup,
1211
+ outputBindGroup
1212
+ ];
1213
+ }
1214
+ createPass(encoder, clear, tiles, timing) {
1215
+ // Update light buffer.
1216
+ if (this._needsUpdateAnalyticLightsBuffer) {
1217
+ const lightsBufferData = packAnalyticsLightsToBuffer(this._analyticLights, this._materialData.getLightTexturesIndices(), this._analyticLightSelectionPmfs, this._iesProfileTexelOffsets);
1218
+ this._device.queue.writeBuffer(this._uniforms.analyticLights, 0, lightsBufferData.buffer, 0, lightsBufferData.buffer.byteLength);
1219
+ this._needsUpdateAnalyticLightsBuffer = false;
1220
+ }
1221
+ const dispatchWidth = Math.ceil(this._width / tiles[0]);
1222
+ const dispatchHeight = Math.ceil(this._height / tiles[1]);
1223
+ // Update other uniforms
1224
+ this._device.queue.writeBuffer(this._uniforms.rtUniform, 0, this._rtUniform.buffer);
1225
+ if (clear) {
1226
+ encoder.clearBuffer(this._outColorBuffer);
1227
+ encoder.clearBuffer(this._outNormalBuffer);
1228
+ encoder.clearBuffer(this._outAlbedoBuffer);
1229
+ encoder.clearBuffer(this._outShadowCatcherBuffer);
1230
+ }
1231
+ const descriptor = {};
1232
+ const querySet = timing?.querySet;
1233
+ if (querySet) {
1234
+ descriptor.timestampWrites = {
1235
+ querySet,
1236
+ beginningOfPassWriteIndex: 0,
1237
+ endOfPassWriteIndex: 1
1238
+ };
1239
+ }
1240
+ const pass = encoder.beginComputePass(descriptor);
1241
+ if (!this._rayTracePipeline || !this._rayTraceBindGroups) {
1242
+ throw new Error('WebGPU ray tracing pass was submitted before it was ready.');
1243
+ }
1244
+ pass.setPipeline(this._rayTracePipeline);
1245
+ this._rayTraceBindGroups.forEach((bindGroup, idx) => {
1246
+ pass.setBindGroup(idx, bindGroup);
1247
+ });
1248
+ pass.dispatchWorkgroups(Math.ceil(dispatchWidth / WORKGROUP_SIZE), Math.ceil(dispatchHeight / WORKGROUP_SIZE), 1);
1249
+ pass.end();
1250
+ }
1251
+ async getDebugBVHData() {
1252
+ if (!this._debugBVHBuffer) {
1253
+ throw new Error(`software-bvh exposes no CPU-readable BVH debug buffer`);
1254
+ }
1255
+ return {
1256
+ buffer: this._debugBVHBuffer
1257
+ };
1258
+ }
1259
+ getDiagnostics() {
1260
+ return {
1261
+ textureAtlas: this._materialData?.textureAtlasStats,
1262
+ shaderCompilation: this._shaderCompilationDiagnostics,
1263
+ lightSampling: {
1264
+ emissiveTriangles: this._emissiveTriangles.length,
1265
+ activeChoices: this._rtUniform.views.lightChoiceCount[0],
1266
+ totalWeight: this._rtUniform.views.lightWeightTotal[0],
1267
+ domains: this._lightDomainDiagnostics
1268
+ },
1269
+ bvh: {
1270
+ tlasTraversalStackSize: this._constants.TLAS_STACK_SIZE,
1271
+ blasTraversalStackSize: this._constants.BLAS_STACK_SIZE
1272
+ }
1273
+ };
1274
+ }
1275
+ setAlphaBlendStrategy(strategy) {
1276
+ const next = strategy === 'deterministic-source-over'
1277
+ ? strategy
1278
+ : 'stochastic';
1279
+ if (next === this._alphaBlendStrategy)
1280
+ return false;
1281
+ this._alphaBlendStrategy = next;
1282
+ if (this._constants.HAS_ALPHA_BLEND) {
1283
+ this._computeShaderModule = undefined;
1284
+ this._rayTracePipeline = undefined;
1285
+ this._rayTracePipelineLayout = undefined;
1286
+ this._rayTraceBindGroups = undefined;
1287
+ this._prevPipelineConstants = {};
1288
+ this._failedPipelineSignature = undefined;
1289
+ this._needsUpdateBindGroups = true;
1290
+ }
1291
+ return true;
1292
+ }
1293
+ hasDepthOfField() {
1294
+ return this._apertureRadius > 0;
1295
+ }
1296
+ setBounce(bounce) {
1297
+ const next = normalizeBounces(bounce);
1298
+ const changed = this._bounces !== next;
1299
+ this._bounces = next;
1300
+ this._rtUniform.views.bounces[0] = next;
1301
+ return changed;
1302
+ }
1303
+ setDirectLightCandidates(candidates) {
1304
+ const next = normalizeDirectLightCandidates(candidates);
1305
+ const changed = this._directLightCandidates !== next;
1306
+ this._directLightCandidates = next;
1307
+ this._rtUniform.views.directLightCandidates[0] = next;
1308
+ return changed;
1309
+ }
1310
+ setClamping(clamping) {
1311
+ const changed = this._clamping !== clamping;
1312
+ this._rtUniform.views.clamping[0] = clamping ?? 0;
1313
+ this._clamping = clamping;
1314
+ return changed;
1315
+ }
1316
+ setBevelSamples(samples) {
1317
+ const next = normalizeBevelSamples(samples);
1318
+ const changed = this._bevelSamples !== next;
1319
+ if (changed) {
1320
+ this._bevelSamples = next;
1321
+ this._constants.BEVEL_SAMPLES = next;
1322
+ }
1323
+ return changed;
1324
+ }
1325
+ setSamplingPattern(pattern, maxSamples, sequenceSeed = 0) {
1326
+ const code = samplingPatternCode(pattern);
1327
+ const logSPP = zSobolLog2SPP(code === 1 ? maxSamples - 1 : maxSamples);
1328
+ const views = this._rtUniform.views;
1329
+ const changed = this._constants.SAMPLING_PATTERN !== code ||
1330
+ views.sequenceSeed[0] !== (sequenceSeed >>> 0) ||
1331
+ (code === 1 && views.zSobolLogSPP[0] !== logSPP);
1332
+ this._constants.SAMPLING_PATTERN = code;
1333
+ views.sequenceSeed[0] = sequenceSeed;
1334
+ views.zSobolLogSPP[0] = logSPP;
1335
+ return changed;
1336
+ }
1337
+ setSampleSequenceOffset(offset) {
1338
+ this._rtUniform.views.sampleSequenceOffset[0] = offset;
1339
+ }
1340
+ setVarianceMoments(enabled) {
1341
+ this._rtUniform.views.varianceMoments[0] = enabled ? 1 : 0;
1342
+ }
1343
+ setAdaptiveSamplingTargets(targets) {
1344
+ this._rtUniform.views.adaptiveSampling[0] = targets ? 1 : 0;
1345
+ if (!targets)
1346
+ return;
1347
+ const { width, height, data } = targets;
1348
+ if (width <= 0 || height <= 0 || data.length !== width * height) {
1349
+ throw new Error('Adaptive sample targets do not match their dimensions.');
1350
+ }
1351
+ this._ensureAdaptiveSamplingTexture(width, height);
1352
+ this._device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, data, { bytesPerRow: width * Float32Array.BYTES_PER_ELEMENT }, [width, height]);
1353
+ }
1354
+ setQuality(options) {
1355
+ this._rtUniform.views.bounces[0] = normalizeBounces(options.bounces);
1356
+ this._rtUniform.views.bevel[0] = +options.bevel;
1357
+ this._rtUniform.views.dispersionWavelengths[0] = options.dispersionWavelengths;
1358
+ this._rtUniform.views.directLightCandidates[0] = normalizeDirectLightCandidates(options.directLightCandidates);
1359
+ }
1360
+ _setEnvMap(envImage) {
1361
+ const device = this._device;
1362
+ // Always create env tex
1363
+ if (!environmentMapSourcesMatch(envImage, this._envImageSource) ||
1364
+ !this._envTex) {
1365
+ this._envImageSource = envImage;
1366
+ this._envTex?.destroy();
1367
+ this._envDistributionTex?.destroy();
1368
+ const distribution = envImage && envMapDistribution(envImage);
1369
+ const usage = GPUTextureUsage.TEXTURE_BINDING |
1370
+ GPUTextureUsage.COPY_DST |
1371
+ GPUTextureUsage.RENDER_ATTACHMENT;
1372
+ this._envTex = device.createTexture({
1373
+ format: 'rgba32float',
1374
+ usage,
1375
+ label: 'Env',
1376
+ size: {
1377
+ width: envImage ? envImage.width : 1,
1378
+ height: envImage ? envImage.height : 1,
1379
+ depthOrArrayLayers: 1
1380
+ }
1381
+ });
1382
+ this._envDistributionTex = device.createTexture({
1383
+ format: 'rg32float',
1384
+ usage,
1385
+ label: 'Env Distribution',
1386
+ size: {
1387
+ width: distribution ? distribution.width : 1,
1388
+ height: distribution ? distribution.height : 1,
1389
+ depthOrArrayLayers: 1
1390
+ }
1391
+ });
1392
+ if (envImage && distribution) {
1393
+ device.queue.writeTexture({
1394
+ texture: this._envTex
1395
+ }, envImage.data.buffer, {
1396
+ bytesPerRow: envImage.width * 4 * 4
1397
+ }, {
1398
+ width: envImage.width,
1399
+ height: envImage.height,
1400
+ depthOrArrayLayers: 1
1401
+ });
1402
+ device.queue.writeTexture({
1403
+ texture: this._envDistributionTex
1404
+ }, distribution.data.buffer, {
1405
+ bytesPerRow: distribution.width * 4 * 2
1406
+ }, {
1407
+ width: distribution.width,
1408
+ height: distribution.height,
1409
+ depthOrArrayLayers: 1
1410
+ });
1411
+ }
1412
+ this._needsUpdateBindGroups = true;
1413
+ }
1414
+ this._constants.USE_ENVMAP = !!envImage;
1415
+ }
1416
+ setMeshes(meshes, update) {
1417
+ for (const mesh of update?.colors ?? [])
1418
+ this._dirtyColorMeshes.add(mesh);
1419
+ if (update && this._preparedMeshData && this._materialData &&
1420
+ !this._needsUpdateMeshData && !this._needsUpdateMeshMembership && meshes === this._meshes) {
1421
+ const materialUpdate = this._materialChanges.classify(update.materials ?? []);
1422
+ if (materialUpdate.level !== 'geometry' && canUpdateGeometries(this._preparedMeshData, update.geometries ?? [])) {
1423
+ if (update.materials?.length) {
1424
+ const pending = this._pendingMaterialUpdate;
1425
+ this._pendingMaterialUpdate = {
1426
+ level: pending?.level === 'textures' ? 'textures' : materialUpdate.level,
1427
+ emission: !!pending?.emission || materialUpdate.emission,
1428
+ emissiveMap: !!pending?.emissiveMap || materialUpdate.emissiveMap,
1429
+ materials: new Set([...(pending?.materials ?? []), ...update.materials])
1430
+ };
1431
+ this._meshSnapshot.forEach(item => { item.materialKeys = item.materials.map(getMaterialHashKey); });
1432
+ }
1433
+ for (const geometry of update.geometries ?? [])
1434
+ this._pendingGeometryUpdates.add(geometry);
1435
+ for (const item of this._meshSnapshot)
1436
+ if (this._pendingGeometryUpdates.has(item.geometry))
1437
+ item.geometryVersion = item.geometry.version;
1438
+ for (const mesh of update.transforms ?? []) {
1439
+ const item = this._snapshotByMesh.get(mesh);
1440
+ if (!item)
1441
+ continue;
1442
+ item.matrix = Array.from(mesh.transform);
1443
+ this._dirtyTransformMeshes.add(mesh);
1444
+ this._needsRefitTLAS = true;
1445
+ }
1446
+ return !!update.materials?.length || !!update.transforms?.length || !!update.colors?.length || !!update.geometries?.length;
1447
+ }
1448
+ }
1449
+ const transformOnlyMeshes = update?.materials?.length || update?.colors?.length ? undefined : update?.transforms;
1450
+ const materialChange = !!update?.materials?.length;
1451
+ if (transformOnlyMeshes && !transformOnlyMeshes.some(mesh => mesh instanceof InstancedMesh) &&
1452
+ meshes === this._meshes &&
1453
+ this._meshSnapshot.length === meshes.length &&
1454
+ this._meshSnapshot.every((snapshot) => snapshot.geometryVersion === snapshot.geometry.version &&
1455
+ snapshot.bvh === getGeometryBvh(snapshot.geometry))) {
1456
+ let transformChange = false;
1457
+ for (const mesh of transformOnlyMeshes) {
1458
+ const snapshot = this._snapshotByMesh.get(mesh);
1459
+ if (!snapshot)
1460
+ continue;
1461
+ const matrix = mesh.transform;
1462
+ if (snapshot.matrix.length === matrix.length &&
1463
+ snapshot.matrix.every((value, index) => value === matrix[index])) {
1464
+ continue;
1465
+ }
1466
+ snapshot.matrix = Array.from(matrix);
1467
+ this._dirtyTransformMeshes.add(mesh);
1468
+ transformChange = true;
1469
+ }
1470
+ if (transformChange) {
1471
+ this._needsRefitTLAS = true;
1472
+ }
1473
+ return transformChange;
1474
+ }
1475
+ const previousSnapshot = this._meshSnapshot;
1476
+ const nextSnapshot = meshes.map((mesh) => ({
1477
+ mesh,
1478
+ instanceCount: mesh instanceof InstancedMesh ? mesh.count : 1,
1479
+ instanceVersion: mesh instanceof InstancedMesh ? mesh.transformsVersion : 0,
1480
+ instanceTransforms: mesh instanceof InstancedMesh ? mesh.transforms : undefined,
1481
+ instanceColors: mesh instanceof InstancedMesh ? mesh.colors : undefined,
1482
+ colorVersion: mesh instanceof InstancedMesh ? mesh.colorsVersion : 0,
1483
+ geometry: mesh.geometry,
1484
+ bvh: getGeometryBvh(mesh.geometry),
1485
+ materials: mesh.materials.slice(),
1486
+ materialKeys: mesh.materials.map(getMaterialHashKey),
1487
+ geometryVersion: mesh.geometry.version,
1488
+ positionArray: mesh.geometry.position.array,
1489
+ positionItemSize: mesh.geometry.position.itemSize,
1490
+ indexArray: mesh.geometry.indices.array,
1491
+ indexItemSize: mesh.geometry.indices.itemSize,
1492
+ materialIndexArray: mesh.geometry.materialIndex?.array,
1493
+ materialIndexItemSize: mesh.geometry.materialIndex?.itemSize,
1494
+ matrix: Array.from(mesh.transform)
1495
+ }));
1496
+ const membershipChanged = nextSnapshot.length !== previousSnapshot.length ||
1497
+ nextSnapshot.some((item, i) => item.mesh !== previousSnapshot[i]?.mesh || item.instanceCount !== previousSnapshot[i]?.instanceCount ||
1498
+ item.materials.some((material, k) => material !== previousSnapshot[i]?.materials[k]));
1499
+ const membership = !this._needsUpdateMeshData && membershipChanged && this._preparedMeshData
1500
+ ? this._membershipCache.update(this._preparedMeshData, meshes) : undefined;
1501
+ if (membership) {
1502
+ const versions = new Map(previousSnapshot.flatMap(item => item.materials.map((m, i) => [m, item.materialKeys[i]])));
1503
+ const materialsChanged = membership.materials.length !== this._preparedMeshData.materials.length ||
1504
+ membership.materials.some((m, i) => m !== this._preparedMeshData.materials[i] ||
1505
+ versions.has(m) && getMaterialHashKey(m) !== versions.get(m));
1506
+ this._pendingMembershipData = membership;
1507
+ this._meshes = meshes;
1508
+ this._meshSnapshot = nextSnapshot;
1509
+ this._snapshotByMesh = new WeakMap();
1510
+ nextSnapshot.forEach(item => this._snapshotByMesh.set(item.mesh, item));
1511
+ this._needsUpdateMeshMembership = true;
1512
+ this._needsRefitTLAS = false;
1513
+ this._dirtyTransformMeshes.clear();
1514
+ if (materialsChanged)
1515
+ this._pendingMaterialUpdate = { level: 'textures', emission: true,
1516
+ emissiveMap: true, materials: new Set(membership.materials) };
1517
+ return true;
1518
+ }
1519
+ const membershipOnlyChange = !!this._residentPreparedMeshData &&
1520
+ (nextSnapshot.length !== previousSnapshot.length ||
1521
+ nextSnapshot.some((next, index) => next.mesh !== previousSnapshot[index]?.mesh)) &&
1522
+ nextSnapshot.every((next) => {
1523
+ const resident = this._residentSnapshotByMesh.get(next.mesh);
1524
+ return (!!resident &&
1525
+ next.colorVersion === resident.colorVersion && next.instanceColors === resident.instanceColors &&
1526
+ next.geometry === resident.geometry &&
1527
+ next.bvh === resident.bvh &&
1528
+ next.materials.length === resident.materials.length &&
1529
+ next.materials.every((material, index) => material === resident.materials[index]) &&
1530
+ next.materialKeys.every((hash, index) => hash === resident.materialKeys[index]) &&
1531
+ next.geometryVersion === resident.geometryVersion &&
1532
+ this._materialChanges.classify(next.materials).level === 'params' &&
1533
+ next.positionArray === resident.positionArray &&
1534
+ next.indexArray === resident.indexArray &&
1535
+ next.materialIndexArray === resident.materialIndexArray &&
1536
+ next.matrix.every((value, index) => value === resident.matrix[index]));
1537
+ });
1538
+ if (membershipOnlyChange) {
1539
+ this._meshes = meshes;
1540
+ this._meshSnapshot = nextSnapshot;
1541
+ this._snapshotByMesh = new WeakMap();
1542
+ nextSnapshot.forEach((snapshot) => {
1543
+ this._snapshotByMesh.set(snapshot.mesh, snapshot);
1544
+ });
1545
+ this._needsUpdateMeshMembership = true;
1546
+ this._needsRefitTLAS = false;
1547
+ this._dirtyTransformMeshes.clear();
1548
+ return true;
1549
+ }
1550
+ const topologyOrMaterialChange = nextSnapshot.length !== previousSnapshot.length ||
1551
+ nextSnapshot.some((next, i) => {
1552
+ const prev = previousSnapshot[i];
1553
+ return (!prev ||
1554
+ next.mesh !== prev.mesh || next.instanceCount !== prev.instanceCount ||
1555
+ next.geometry !== prev.geometry ||
1556
+ next.bvh !== prev.bvh || next.colorVersion !== prev.colorVersion || next.instanceColors !== prev.instanceColors ||
1557
+ next.materials.length !== prev.materials.length ||
1558
+ next.materials.some((material, k) => material !== prev.materials[k]) ||
1559
+ next.materialKeys.some((hash, k) => hash !== prev.materialKeys[k]) ||
1560
+ next.positionArray !== prev.positionArray ||
1561
+ next.positionArray.length !== prev.positionArray.length ||
1562
+ next.positionItemSize !== prev.positionItemSize ||
1563
+ next.indexArray !== prev.indexArray ||
1564
+ next.indexArray.length !== prev.indexArray.length ||
1565
+ next.indexItemSize !== prev.indexItemSize ||
1566
+ next.materialIndexArray !== prev.materialIndexArray ||
1567
+ next.materialIndexArray?.length !== prev.materialIndexArray?.length ||
1568
+ next.materialIndexItemSize !== prev.materialIndexItemSize);
1569
+ });
1570
+ const geometryChange = !topologyOrMaterialChange &&
1571
+ nextSnapshot.some((next, i) => next.geometryVersion !== previousSnapshot[i].geometryVersion);
1572
+ const structuralChange = topologyOrMaterialChange || geometryChange;
1573
+ const transformChange = !topologyOrMaterialChange &&
1574
+ nextSnapshot.some((next, i) => next.instanceVersion !== previousSnapshot[i].instanceVersion || next.instanceTransforms !== previousSnapshot[i].instanceTransforms || next.matrix.some((value, k) => value !== previousSnapshot[i].matrix[k]));
1575
+ this._meshes = meshes;
1576
+ nextSnapshot.forEach((next, i) => {
1577
+ const prev = previousSnapshot[i];
1578
+ if (prev?.geometry === next.geometry && (prev.positionArray !== next.positionArray || prev.indexArray !== next.indexArray)) {
1579
+ setGeometryBvh(next.geometry, undefined);
1580
+ this._geometriesNeedRefit.add(next.geometry);
1581
+ }
1582
+ // A mesh can be animated before this renderer sees it for the first
1583
+ // time. Its precomputed BLAS may then describe an older pose as well.
1584
+ if (next.geometryVersion > 0 && (!prev || next.geometry !== prev.geometry ||
1585
+ next.geometryVersion !== prev.geometryVersion)) {
1586
+ this._geometriesNeedRefit.add(next.geometry);
1587
+ }
1588
+ });
1589
+ this._meshSnapshot = nextSnapshot;
1590
+ this._snapshotByMesh = new WeakMap();
1591
+ nextSnapshot.forEach((snapshot) => {
1592
+ this._snapshotByMesh.set(snapshot.mesh, snapshot);
1593
+ });
1594
+ if (structuralChange) {
1595
+ this._needsUpdateMeshData = true;
1596
+ this._needsUpdateMeshMembership = false;
1597
+ this._needsRefitTLAS = false;
1598
+ this._dirtyTransformMeshes.clear();
1599
+ }
1600
+ else if (transformChange) {
1601
+ this._needsRefitTLAS = true;
1602
+ nextSnapshot.forEach((next, index) => {
1603
+ if (next.instanceVersion !== previousSnapshot[index].instanceVersion || next.instanceTransforms !== previousSnapshot[index].instanceTransforms || next.matrix.some((value, matrixIndex) => value !== previousSnapshot[index]?.matrix[matrixIndex])) {
1604
+ this._dirtyTransformMeshes.add(next.mesh);
1605
+ }
1606
+ });
1607
+ }
1608
+ if (materialChange || !update && !structuralChange && !transformChange) {
1609
+ this._needsUpdateMeshData = true;
1610
+ }
1611
+ return structuralChange || transformChange || materialChange || !update;
1612
+ }
1613
+ setTextureAtlasBudget(maxBytes) {
1614
+ const next = Number.isFinite(maxBytes)
1615
+ ? Math.max(4, Math.floor(maxBytes))
1616
+ : DEFAULT_MAX_TEXTURE_ARRAY_BYTES;
1617
+ if (next === this._maxTextureArrayBytes)
1618
+ return false;
1619
+ this._maxTextureArrayBytes = next;
1620
+ this._needsUpdateMeshData = true;
1621
+ return true;
1622
+ }
1623
+ setBackground(background) {
1624
+ this._constants.USE_BACKGROUND = !!background;
1625
+ const backgroundImage = background && !Array.isArray(background)
1626
+ ? generateBackgroundMapFromSceneBackground(background)
1627
+ : undefined;
1628
+ this._constants.USE_BACKGROUND_MAP = !!backgroundImage;
1629
+ if (Array.isArray(background)) {
1630
+ this._rtUniform.views.backgroundColor.set(new F32(background));
1631
+ }
1632
+ const grounded = background &&
1633
+ !Array.isArray(background) &&
1634
+ 'type' in background &&
1635
+ background.type === 'GroundedSkybox'
1636
+ ? background
1637
+ : undefined;
1638
+ this._rtUniform.views.groundedBackgroundParams.set(grounded
1639
+ ? [
1640
+ Math.max(grounded.height, 0.0001),
1641
+ Math.max(grounded.radius, 0.0001),
1642
+ grounded.groundHeight ?? 0,
1643
+ 1
1644
+ ]
1645
+ : [0, 0, 0, 0]);
1646
+ this._rtUniform.views.groundedBackgroundCenter.set(grounded?.center ?? [0, 0]);
1647
+ this._constants.USE_GROUNDED_BACKGROUND = !!grounded;
1648
+ this._rtUniform.views.backgroundFlags[0] =
1649
+ ((background ? 1 : 0) | (backgroundImage ? 2 : 0) | (grounded ? 4 : 0)) |
1650
+ (this._rtUniform.views.backgroundFlags[0] & 0x80000000);
1651
+ this._setBackgroundMap(backgroundImage);
1652
+ }
1653
+ _setBackgroundMap(backgroundImage) {
1654
+ if (backgroundImage === this._backgroundImageSource && this._backgroundTex) {
1655
+ return;
1656
+ }
1657
+ this._backgroundImageSource = backgroundImage;
1658
+ this._backgroundTex?.destroy();
1659
+ const width = backgroundImage?.width ?? 1;
1660
+ const height = backgroundImage?.height ?? 1;
1661
+ this._backgroundTex = this._device.createTexture({
1662
+ format: 'rgba32float',
1663
+ usage: GPUTextureUsage.TEXTURE_BINDING |
1664
+ GPUTextureUsage.COPY_DST |
1665
+ GPUTextureUsage.RENDER_ATTACHMENT,
1666
+ label: 'Background',
1667
+ size: { width, height, depthOrArrayLayers: 1 }
1668
+ });
1669
+ if (backgroundImage) {
1670
+ const source = backgroundImage.data;
1671
+ const data = source instanceof Float32Array
1672
+ ? source
1673
+ : Float32Array.from(source, (value) => (source instanceof Uint8Array || source instanceof Uint8ClampedArray) ? value / 255 : value);
1674
+ this._device.queue.writeTexture({ texture: this._backgroundTex }, data.buffer, { bytesPerRow: width * 4 * 4 }, { width, height, depthOrArrayLayers: 1 });
1675
+ }
1676
+ this._needsUpdateBindGroups = true;
1677
+ }
1678
+ _updateLightSamplingData() {
1679
+ const choices = this._lightChoices;
1680
+ choices.reserve(1 + this._analyticLights.length + this._emissiveTriangles.length);
1681
+ choices.push(LIGHT_CHOICE_ENVIRONMENT, 0, estimateEnvironmentPower(this._envImageSource));
1682
+ this._analyticLights.forEach((light, i) => choices.push(LIGHT_CHOICE_ANALYTIC, i, estimateAnalyticLightPower(light)));
1683
+ this._emissiveTriangles.forEach((triangle, i) => choices.push(LIGHT_CHOICE_EMISSIVE_TRIANGLE, i, triangle.area * triangle.samplingLuminance));
1684
+ const distribution = buildLightAliasTable(choices);
1685
+ const domainDiagnostics = {
1686
+ environment: { count: 0, weight: 0, pmf: 0 },
1687
+ infinite: { count: 0, weight: 0, pmf: 0 },
1688
+ localAndEmissive: { count: 0, weight: 0, pmf: 0 }
1689
+ };
1690
+ this._analyticLightSelectionPmfs = new Array(this._analyticLights.length).fill(0);
1691
+ this._rtUniform.views.envLightPmf[0] = 0;
1692
+ const domains = [domainDiagnostics.environment, domainDiagnostics.infinite, domainDiagnostics.localAndEmissive];
1693
+ for (let i = 0; i < choices.count; i++) {
1694
+ const type = choices.sourceTypes[i], source = choices.sourceIndices[i];
1695
+ const pmf = distribution.pmfs[i];
1696
+ const domainIndex = type === LIGHT_CHOICE_ENVIRONMENT ? LIGHT_DOMAIN_ENVIRONMENT :
1697
+ type === LIGHT_CHOICE_ANALYTIC && this._analyticLights[source]?.type === 'DirectionalLight' ?
1698
+ LIGHT_DOMAIN_INFINITE : LIGHT_DOMAIN_LOCAL_AND_EMISSIVE;
1699
+ const domain = domains[domainIndex];
1700
+ domain.count++;
1701
+ domain.weight += choices.weights[i];
1702
+ domain.pmf += pmf;
1703
+ if (type === LIGHT_CHOICE_ENVIRONMENT)
1704
+ this._rtUniform.views.envLightPmf[0] = pmf;
1705
+ else if (type === LIGHT_CHOICE_ANALYTIC)
1706
+ this._analyticLightSelectionPmfs[source] = pmf;
1707
+ else
1708
+ this._emissiveTriangles[source].selectionPmf = pmf;
1709
+ }
1710
+ this._lightDomainDiagnostics = domainDiagnostics;
1711
+ this._rtUniform.views.lightChoiceCount[0] = choices.count;
1712
+ this._rtUniform.views.lightWeightTotal[0] = distribution.totalWeight;
1713
+ const emissiveTexels = this._emissiveTriangles.length * 6;
1714
+ const choiceTexels = Math.max(choices.count, 1);
1715
+ const profileStartTexel = emissiveTexels + choiceTexels;
1716
+ const packedIES = packIESProfiles(this._analyticLights, profileStartTexel);
1717
+ this._iesProfileTexelOffsets = packedIES.offsets;
1718
+ const totalTexels = Math.max(profileStartTexel + packedIES.texelCount, 1);
1719
+ const maxDimension = this._device.limits.maxTextureDimension2D;
1720
+ const width = Math.min(maxDimension, Math.max(16, Math.ceil(Math.sqrt(totalTexels) / 16) * 16));
1721
+ const height = Math.ceil(totalTexels / width);
1722
+ if (height > maxDimension) {
1723
+ throw new Error('Too many emissive triangles for the light sampling texture.');
1724
+ }
1725
+ const resized = !this._lightSamplingTexture || width !== this._lightSamplingTextureSize[0] || height !== this._lightSamplingTextureSize[1];
1726
+ if (!this._lightSamplingTexture ||
1727
+ width !== this._lightSamplingTextureSize[0] ||
1728
+ height !== this._lightSamplingTextureSize[1]) {
1729
+ this._lightSamplingTexture?.destroy();
1730
+ this._lightSamplingTexture = this._device.createTexture({
1731
+ label: 'Light sampling data',
1732
+ size: [width, height],
1733
+ format: 'rgba32float',
1734
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
1735
+ });
1736
+ this._lightSamplingTextureSize = [width, height];
1737
+ this._needsUpdateBindGroups = true;
1738
+ }
1739
+ const textureData = this._lightSamplingScratch?.length === width * height * 4
1740
+ ? this._lightSamplingScratch : new Float32Array(width * height * 4);
1741
+ textureData.fill(0);
1742
+ if (this._emissiveTriangles.length) {
1743
+ packEmissiveTriangles(this._emissiveTriangles, textureData);
1744
+ }
1745
+ new Uint32Array(textureData.buffer).set(distribution.choices, emissiveTexels * 4);
1746
+ textureData.set(packedIES.data, profileStartTexel * 4);
1747
+ const previous = this._lightSamplingData;
1748
+ const full = resized || !previous || this._lightSamplingTriangleCount !== this._emissiveTriangles.length;
1749
+ const words = new Uint32Array(textureData.buffer);
1750
+ const oldWords = previous && new Uint32Array(previous.buffer);
1751
+ let startRow = -1;
1752
+ for (let row = 0; row <= height; row++) {
1753
+ let changed = row < height && full;
1754
+ if (row < height && !full) {
1755
+ for (let i = row * width * 4; i < (row + 1) * width * 4; i++) {
1756
+ if (words[i] !== oldWords[i]) {
1757
+ changed = true;
1758
+ break;
1759
+ }
1760
+ }
1761
+ }
1762
+ if (changed && startRow < 0)
1763
+ startRow = row;
1764
+ if (!changed && startRow >= 0) {
1765
+ this._device.queue.writeTexture({ texture: this._lightSamplingTexture, origin: [0, startRow] }, textureData.subarray(startRow * width * 4, row * width * 4), { bytesPerRow: width * 16 }, { width, height: row - startRow });
1766
+ startRow = -1;
1767
+ }
1768
+ }
1769
+ this._lightSamplingScratch = previous;
1770
+ this._lightSamplingData = textureData;
1771
+ this._lightSamplingTriangleCount = this._emissiveTriangles.length;
1772
+ this._rtUniform.views.lightSamplingTextureWidth[0] = width;
1773
+ this._rtUniform.views.lightChoiceTexelOffset[0] = emissiveTexels;
1774
+ this._needsUpdateAnalyticLightsBuffer = true;
1775
+ this._needsUpdateLightSamplingData = false;
1776
+ }
1777
+ /** Uploads the combined TLAS/BLAS buffer and the instance records. */
1778
+ _uploadBVH(tlas, blases) {
1779
+ const stacks = getTraversalStackSizes(tlas, blases);
1780
+ this._constants.TLAS_STACK_SIZE = retainedShaderCapacity(stacks.tlas, Number(this._constants.TLAS_STACK_SIZE ?? 0));
1781
+ this._constants.BLAS_STACK_SIZE = retainedShaderCapacity(stacks.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32);
1782
+ const tlasBuffer = new Float32Array(tlas.buffer);
1783
+ const blasBuffer = new Float32Array(concatBLASBuffers(blases));
1784
+ const bvhData = new Float32Array(tlasBuffer.length + blasBuffer.length);
1785
+ const blasOffset = tlasBuffer.length / 4;
1786
+ bvhData.set(tlasBuffer);
1787
+ bvhData.set(blasBuffer, blasOffset * 4);
1788
+ const bvhBuffer = this._updateGPUBuffer('BVH', this._bvhBuffer, bvhData);
1789
+ const instancesBuffer = this._updateGPUBuffer('Instances', this._instancesBuffer, new Float32Array(tlas.instancesBuffer));
1790
+ if (bvhBuffer !== this._bvhBuffer || instancesBuffer !== this._instancesBuffer) {
1791
+ this._needsUpdateBindGroups = true;
1792
+ }
1793
+ this._bvhBuffer = bvhBuffer;
1794
+ this._instancesBuffer = instancesBuffer;
1795
+ this._debugBVHBuffer = bvhData.buffer.slice(0);
1796
+ this._rtUniform.views.blasOffset.set([blasOffset]);
1797
+ }
1798
+ _updateMeshData() {
1799
+ this._pendingGeometryUpdates.clear();
1800
+ this._emissiveCache = new EmissiveTriangleCache();
1801
+ const meshes = this._meshes;
1802
+ for (const geometry of this._geometriesNeedRefit) {
1803
+ const usesDisplacement = meshes.some((mesh) => mesh.geometry === geometry &&
1804
+ !!mesh.materials[0]?.displacementMap &&
1805
+ Math.abs(mesh.materials[0].displacementScale) > 1e-6);
1806
+ if (usesDisplacement || geometryTopologyChanged(this._preparedMeshData, geometry) || !getGeometryBvh(geometry) || !refitBLAS(getGeometryBvh(geometry), geometry)) {
1807
+ setGeometryBvh(geometry, undefined);
1808
+ }
1809
+ }
1810
+ this._geometriesNeedRefit.clear();
1811
+ // TODO check bvh needs to change
1812
+ const res = updateMeshInstances(meshes);
1813
+ this._preparedMeshData = res;
1814
+ this._membershipCache.capture(res);
1815
+ this._pendingMembershipData = undefined;
1816
+ this._needsUpdateMeshMembership = false;
1817
+ const mergedGeometry = res.geometry;
1818
+ const materials = res.materials;
1819
+ const isEmpty = meshes.length === 0 || res.instances.length === 0;
1820
+ const tlas = buildTLAS(res.instances, res.blases);
1821
+ this._tlas = tlas;
1822
+ this._uploadBVH(tlas, res.blases);
1823
+ const materialData = (this._materialData = new MaterialTable(materials, this._analyticLights, {
1824
+ maxTextureArrayBytes: this._maxTextureArrayBytes,
1825
+ maxTextureArrayLayers: this._device.limits.maxTextureArrayLayers,
1826
+ maxTextureDimension2D: this._device.limits.maxTextureDimension2D
1827
+ }));
1828
+ this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, materials);
1829
+ this._needsUpdateLightSamplingData = true;
1830
+ const attrNames = [
1831
+ 'position',
1832
+ 'normal',
1833
+ 'uv',
1834
+ 'uv1',
1835
+ 'color',
1836
+ 'tangent',
1837
+ 'materialIndex'
1838
+ ];
1839
+ const attributes = attrNames.map((a) => mergedGeometry[a]);
1840
+ const fixedSize = 4;
1841
+ let stride = 0;
1842
+ const offsets = attrNames.map((attrName, idx) => {
1843
+ const attr = attributes[idx];
1844
+ const offset = stride;
1845
+ if (attr) {
1846
+ stride += fixedSize;
1847
+ }
1848
+ return offset;
1849
+ });
1850
+ const attrBufferData = new F32(mergedGeometry.vertexCount * stride);
1851
+ packVertexAttributes(mergedGeometry, attrBufferData, stride, offsets, 0, mergedGeometry.vertexCount);
1852
+ this._attributeData = attrBufferData;
1853
+ this._attributeStride = stride;
1854
+ this._attributeOffsets = offsets;
1855
+ this._attrBuffers = this._updateGPUBuffer('Attributes', this._attrBuffers, attrBufferData);
1856
+ this._materialsBuffer = this._updateGPUBuffer('Materials', this._materialsBuffer, materialData.getBufferArray(),
1857
+ // WGSL requires a runtime array binding to contain at least one
1858
+ // MaterialBuffer element, including while a
1859
+ // scene reload temporarily has no material payload.
1860
+ MATERIAL_FLOAT_COUNT * 4);
1861
+ const traversalStackSizes = getTraversalStackSizes(tlas, res.blases);
1862
+ const blasLayoutBits = {
1863
+ bvh2: 1,
1864
+ bvh4: 2,
1865
+ 'bvh-gpu': 4,
1866
+ cwbvh: 8
1867
+ };
1868
+ this._blasLayoutMask = res.blases.reduce((mask, blas) => mask | blasLayoutBits[blas.bvh.layout ?? 'bvh2'], 0);
1869
+ Object.assign(this._constants, {
1870
+ TLAS_STACK_SIZE: retainedShaderCapacity(traversalStackSizes.tlas, Number(this._constants.TLAS_STACK_SIZE ?? 0)),
1871
+ BLAS_STACK_SIZE: retainedShaderCapacity(traversalStackSizes.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32),
1872
+ USE_UV: mergedGeometry.uv != null,
1873
+ USE_VERTEX_COLOR: mergedGeometry.color != null,
1874
+ ATTR_STRIDE: stride / fixedSize,
1875
+ ...offsets.reduce((obj, off, idx) => {
1876
+ obj[attrNames[idx].toUpperCase() + '_OFF'] = off / fixedSize;
1877
+ return obj;
1878
+ }, {})
1879
+ });
1880
+ this._updateMaterialConstants(materials, materialData, mergedGeometry);
1881
+ this._updateTextures(materialData);
1882
+ this._needsUpdateBindGroups = true;
1883
+ this._rtUniform.views.isEmpty[0] = +isEmpty;
1884
+ this._meshSnapshot.forEach((snapshot) => {
1885
+ snapshot.bvh = getGeometryBvh(snapshot.geometry);
1886
+ });
1887
+ this._residentPreparedMeshData = res;
1888
+ this._residentSnapshotByMesh = new WeakMap();
1889
+ this._meshSnapshot.forEach((snapshot) => {
1890
+ this._residentSnapshotByMesh.set(snapshot.mesh, snapshot);
1891
+ });
1892
+ this._materialChanges.capture(materials);
1893
+ this._pendingMaterialUpdate = undefined;
1894
+ this._needsUpdateMeshData = false;
1895
+ this._needsUpdateMeshMembership = false;
1896
+ this._needsRefitTLAS = false;
1897
+ }
1898
+ _updateMaterialConstants(materials, materialData, mergedGeometry) {
1899
+ const hasTransmission = materials.some((m) => m.transmissionWeight > 0);
1900
+ if (hasTransmission !== this._hasTransmission) {
1901
+ this._hasTransmission = hasTransmission;
1902
+ }
1903
+ this._hasShadowCatcher = materials.some((m) => m.shadowCatcher);
1904
+ const hasDisplacement = materialData.getDisplacementMaps().length > 0;
1905
+ if (hasDisplacement !== this._hasDisplacement) {
1906
+ this._hasDisplacement = hasDisplacement;
1907
+ // Needs to recreate pipeline layout
1908
+ this._rayTracePipelineLayout = undefined;
1909
+ }
1910
+ const HAS_BEVEL = materials.some((m) => m.bevelSize > 0);
1911
+ const SPECTRAL = materials.some((m) => m.transmissionDispersionScale > 0);
1912
+ const HAS_MEDIUM = materials.some(materialUsesMedium);
1913
+ // Retain the atlas uniform capacity when a stage/material is removed.
1914
+ // Shrinking the array would rebuild every pipeline again on the next edit.
1915
+ const NUM_MAPS = Math.max(Math.ceil(+materialData.defines.NUM_MAPS / 10) * 10, Number(this._constants.NUM_MAPS ?? 0), 20);
1916
+ Object.assign(this._constants, {
1917
+ // Match the atlas UV selector. Keep the second set when any texture
1918
+ // needs it; material/texture edits already rebuild these constants.
1919
+ USE_UV1: materialData.bufferData.mapUvChannel.some(channel => channel > 0.5),
1920
+ // Isotropic OpenPBR constructs its own basis. Normal mapping uses
1921
+ // triangle UV differentials rather than this vertex tangent.
1922
+ USE_TANGENT: mergedGeometry.tangent != null &&
1923
+ materials.some(material => material.specularAnisotropy > 0 || material.coatAnisotropy > 0 || (!!material.coatNormalMap && material.coatWeight > 0)),
1924
+ USE_COAT_NORMAL_FRAMES: materials.some(m => !!m.coatNormalMap && m.coatWeight > 0),
1925
+ USE_ANISOTROPY_DIRECTIONS: materials.some(m => (!!m.coatNormalMap && m.coatWeight > 0) ||
1926
+ m.specularAnisotropy > 0 && (m.specularAnisotropyModel === 'gltf' || !!m.anisotropyMap || m.specularAnisotropyRotation !== 0) ||
1927
+ m.coatAnisotropy > 0 && m.coatAnisotropyRotation !== 0),
1928
+ HAS_MEDIUM,
1929
+ HAS_SHADOW_CATCHER: this._hasShadowCatcher,
1930
+ HAS_THIN_FILM: materials.some((material) => material.thinFilmWeight > 0),
1931
+ HAS_COAT: materials.some((material) => material.coatWeight > 0),
1932
+ HAS_FUZZ: materials.some((material) => material.fuzzWeight > 0),
1933
+ HAS_SUBSURFACE: materials.some((material) => material.subsurfaceWeight > 0),
1934
+ HAS_BEVEL,
1935
+ HAS_ALPHA_MASK: materials.some((m) => resolveMaterialAlphaMode(m) === 'mask'),
1936
+ HAS_ALPHA_BLEND: materials.some((m) => resolveMaterialAlphaMode(m) === 'blend'),
1937
+ HAS_ALPHA: materials.some((m) => resolveMaterialAlphaMode(m) !== 'opaque'),
1938
+ SPECTRAL,
1939
+ BEVEL_SAMPLES: this._bevelSamples,
1940
+ USE_RR: true,
1941
+ // Match WebGL: defer roulette for multiple-scattering SSS paths.
1942
+ RR_MAX: HAS_MEDIUM ? 12 : 2,
1943
+ NUM_UNIFORM_LIGHTS: FIXED_LIGHT_COUNT,
1944
+ ...materialData.defines,
1945
+ NUM_MAPS,
1946
+ ...materialTextureFeatures(materialData.defines),
1947
+ USE_NORMAL_MAPS: +materialData.defines.NUM_NORMAL_MAPS > 0
1948
+ });
1949
+ }
1950
+ _updateGeometryData() {
1951
+ const res = this._preparedMeshData, tlas = this._tlas;
1952
+ const update = updatePreparedGeometries(res, this._pendingGeometryUpdates);
1953
+ for (const { first, count, topologyChanged, deformationOnly } of update.vertices) {
1954
+ packVertexAttributes(res.geometry, this._attributeData, this._attributeStride, this._attributeOffsets, first, count, topologyChanged, deformationOnly);
1955
+ const offset = first * this._attributeStride * 4, size = count * this._attributeStride * 4;
1956
+ this._device.queue.writeBuffer(this._attrBuffers, offset, this._attributeData.buffer, offset, size);
1957
+ }
1958
+ for (const mesh of update.meshes)
1959
+ this._dirtyTransformMeshes.add(mesh);
1960
+ if (update.relocated)
1961
+ for (const binding of res.instanceBindings)
1962
+ this._dirtyTransformMeshes.add(binding.mesh);
1963
+ // Instance BLAS offsets must be refreshed before uploading a resized BVH.
1964
+ if (update.resized) {
1965
+ res.instances = updateMeshInstanceTransforms(res.instanceBindings);
1966
+ refitTLAS(tlas, res.instances, res.blases);
1967
+ this._uploadBVH(tlas, res.blases);
1968
+ }
1969
+ else {
1970
+ const bytes = new Uint8Array(this._debugBVHBuffer);
1971
+ const ranges = [];
1972
+ for (const { blas, offset } of update.blases) {
1973
+ const target = tlas.buffer.byteLength + offset;
1974
+ bytes.set(new Uint8Array(blas.buffer), target);
1975
+ ranges.push({ offset: target, size: blas.buffer.byteLength });
1976
+ }
1977
+ for (const { offset, size } of mergeRefitRanges(ranges)) {
1978
+ this._device.queue.writeBuffer(this._bvhBuffer, offset, this._debugBVHBuffer, offset, size);
1979
+ }
1980
+ }
1981
+ const bits = { bvh2: 1, bvh4: 2, 'bvh-gpu': 4, cwbvh: 8 };
1982
+ this._blasLayoutMask = res.blases.reduce((mask, b) => mask | bits[b.bvh.layout ?? 'bvh2'], 0);
1983
+ const stacks = getTraversalStackSizes(tlas, res.blases);
1984
+ this._constants.BLAS_STACK_SIZE = retainedShaderCapacity(stacks.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32);
1985
+ this._meshSnapshot.forEach(item => { item.bvh = getGeometryBvh(item.geometry); });
1986
+ this._needsRefitTLAS = true;
1987
+ this._pendingGeometryUpdates.clear();
1988
+ }
1989
+ _updateMaterialData() {
1990
+ const update = this._pendingMaterialUpdate;
1991
+ const res = this._preparedMeshData;
1992
+ let materialData = this._materialData;
1993
+ if (update.level === 'textures') {
1994
+ materialData = this._materialData = new MaterialTable(res.materials, this._analyticLights, {
1995
+ maxTextureArrayBytes: this._maxTextureArrayBytes,
1996
+ maxTextureArrayLayers: this._device.limits.maxTextureArrayLayers,
1997
+ maxTextureDimension2D: this._device.limits.maxTextureDimension2D
1998
+ });
1999
+ this._updateTextures(materialData);
2000
+ }
2001
+ else
2002
+ materialData.repackParams(res.materials);
2003
+ this._materialsBuffer = this._updateGPUBuffer('Materials', this._materialsBuffer, materialData.getBufferArray(), MATERIAL_FLOAT_COUNT * 4);
2004
+ this._updateMaterialConstants(res.materials, materialData, res.geometry);
2005
+ if (update.emission || update.emissiveMap) {
2006
+ const resident = this._residentPreparedMeshData;
2007
+ resident.emissiveBindings = resident.instanceBindings.filter(binding => binding.materialIndices.some(index => materialIsEmissive(res.materials[index])));
2008
+ const active = new Set(this._meshes);
2009
+ res.emissiveBindings = resident.emissiveBindings.filter(binding => active.has(binding.mesh));
2010
+ this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
2011
+ this._needsUpdateLightSamplingData = true;
2012
+ }
2013
+ this._needsUpdateBindGroups = true;
2014
+ this._materialChanges.capture([...update.materials]);
2015
+ this._pendingMaterialUpdate = undefined;
2016
+ }
2017
+ /** Rebuilds only instance acceleration data for a cached mesh subset. */
2018
+ _updateMeshMembership() {
2019
+ const resident = this._pendingMembershipData ?? this._residentPreparedMeshData;
2020
+ const pending = this._pendingMembershipData;
2021
+ if (!resident) {
2022
+ this._needsUpdateMeshData = true;
2023
+ this._needsUpdateMeshMembership = false;
2024
+ return;
2025
+ }
2026
+ const activeMeshes = new Set(this._meshes);
2027
+ const instanceBindings = resident.instanceBindings.filter(({ mesh }) => activeMeshes.has(mesh));
2028
+ const emissiveBindings = resident.emissiveBindings.filter(({ mesh }) => activeMeshes.has(mesh));
2029
+ const instances = updateMeshInstanceTransforms(instanceBindings);
2030
+ const res = {
2031
+ ...resident,
2032
+ instances,
2033
+ instanceBindings,
2034
+ emissiveBindings
2035
+ };
2036
+ this._preparedMeshData = res;
2037
+ if (pending)
2038
+ this._residentPreparedMeshData = res;
2039
+ this._pendingMembershipData = undefined;
2040
+ const tlas = buildTLAS(instances, res.blases);
2041
+ this._tlas = tlas;
2042
+ this._uploadBVH(tlas, res.blases);
2043
+ this._emissiveTriangles = this._emissiveCache.collect(emissiveBindings, res.materials);
2044
+ this._needsUpdateLightSamplingData = true;
2045
+ this._rtUniform.views.isEmpty[0] = +(instances.length === 0);
2046
+ this._meshSnapshot.forEach((snapshot) => {
2047
+ snapshot.bvh = getGeometryBvh(snapshot.geometry);
2048
+ });
2049
+ this._needsUpdateMeshMembership = false;
2050
+ }
2051
+ _updateMeshColors() {
2052
+ const res = this._preparedMeshData, tlas = this._tlas;
2053
+ if (res && tlas) {
2054
+ const dirty = updateMeshInstanceColors(res.instanceBindings, res.instances, this._dirtyColorMeshes);
2055
+ const ranges = updateTLASInstanceColors(tlas, res.instances, dirty);
2056
+ if (this._instancesBuffer)
2057
+ for (const { offset, size } of ranges) {
2058
+ this._device.queue.writeBuffer(this._instancesBuffer, offset, tlas.instancesBuffer, offset, size);
2059
+ }
2060
+ }
2061
+ this._dirtyColorMeshes.clear();
2062
+ }
2063
+ _refitMeshInstances() {
2064
+ const res = this._preparedMeshData;
2065
+ const tlas = this._tlas;
2066
+ if (!res || !tlas || !this._bvhBuffer || !this._instancesBuffer) {
2067
+ this._needsUpdateMeshData = true;
2068
+ this._needsRefitTLAS = false;
2069
+ return;
2070
+ }
2071
+ const dirtyIndices = dirtyMeshInstanceIndices(res.instanceBindings, this._dirtyTransformMeshes, res.instances);
2072
+ const incremental = dirtyIndices.size > 0;
2073
+ const previousInstances = res.instances;
2074
+ res.instances = updateMeshInstanceTransforms(res.instanceBindings, incremental ? res.instances : undefined, incremental ? dirtyIndices : undefined);
2075
+ if (!sameInstanceMembership(previousInstances, res.instances)) {
2076
+ this._tlas = buildTLAS(res.instances, res.blases);
2077
+ this._uploadBVH(this._tlas, res.blases);
2078
+ this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
2079
+ this._needsUpdateLightSamplingData = true;
2080
+ this._rtUniform.views.isEmpty[0] = +(res.instances.length === 0);
2081
+ this._dirtyTransformMeshes.clear();
2082
+ this._needsRefitTLAS = false;
2083
+ return;
2084
+ }
2085
+ const ranges = refitTLAS(tlas, res.instances, res.blases, incremental ? dirtyIndices : undefined);
2086
+ const tlasData = new Float32Array(tlas.buffer);
2087
+ for (const { offset, size } of ranges.tlasRanges) {
2088
+ this._device.queue.writeBuffer(this._bvhBuffer, offset, tlas.buffer, offset, size);
2089
+ }
2090
+ for (const { offset, size } of ranges.instanceRanges) {
2091
+ this._device.queue.writeBuffer(this._instancesBuffer, offset, tlas.instancesBuffer, offset, size);
2092
+ }
2093
+ if (this._debugBVHBuffer) {
2094
+ new Float32Array(this._debugBVHBuffer).set(tlasData);
2095
+ }
2096
+ if (!incremental ||
2097
+ res.emissiveBindings.some(({ mesh }) => this._dirtyTransformMeshes.has(mesh))) {
2098
+ this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
2099
+ this._needsUpdateLightSamplingData = true;
2100
+ }
2101
+ this._dirtyTransformMeshes.clear();
2102
+ this._needsRefitTLAS = false;
2103
+ }
2104
+ _updateTextures(materialData) {
2105
+ // Textures
2106
+ // TODO Avoid zero length array.
2107
+ const mapInfo = materialData.bufferData.mapInfo ?? [0, 0, 0, 0];
2108
+ const mapTransform = materialData.bufferData.mapTransform ?? [1, 1, 0, 0];
2109
+ const mapUvChannel = materialData.bufferData.mapUvChannel ?? [0];
2110
+ const mapColorLinear = materialData.bufferData.mapColorLinear;
2111
+ const numMaps = +this._constants.NUM_MAPS;
2112
+ const mapInfoData = new F32(numMaps * 12);
2113
+ for (let i = 0; i < mapInfo.length; i++) {
2114
+ mapInfoData[i] = mapInfo[i];
2115
+ }
2116
+ for (let i = 0; i < mapTransform.length; i++) {
2117
+ mapInfoData[numMaps * 4 + i] = mapTransform[i];
2118
+ }
2119
+ for (let i = 0; i < mapUvChannel.length; i++) {
2120
+ mapInfoData[numMaps * 8 + i * 4] = mapUvChannel[i];
2121
+ mapInfoData[numMaps * 8 + i * 4 + 1] = mapColorLinear[i];
2122
+ }
2123
+ let mapInfoUniform = this._mapInfoUniform;
2124
+ if (!mapInfoUniform || mapInfoUniform.size !== mapInfoData.byteLength) {
2125
+ mapInfoUniform?.destroy();
2126
+ mapInfoUniform = this._uniformBuffer(mapInfoData.byteLength);
2127
+ this._mapInfoUniform = mapInfoUniform;
2128
+ }
2129
+ this._device.queue.writeBuffer(mapInfoUniform, 0, mapInfoData);
2130
+ const texture = materialData.texture;
2131
+ const maps = this._maps;
2132
+ const textureFormat = texture?.storageType === constants.HALF_FLOAT ? 'rgba16float' : 'rgba8unorm';
2133
+ const bytesPerPixel = textureFormat === 'rgba16float' ? 8 : 4;
2134
+ const textureWidth = Math.max(texture?.width ?? 1, 1);
2135
+ const textureHeight = Math.max(texture?.height ?? 1, 1);
2136
+ // TODO Must at least 2 layers.
2137
+ const textureDepth = Math.max(texture?.source.length ?? 1, 2);
2138
+ const maxTextureArrayLayers = this._device.limits.maxTextureArrayLayers;
2139
+ if (textureDepth > maxTextureArrayLayers) {
2140
+ throw new Error(`The scene needs ${textureDepth} texture-array layers, but this GPU device supports ${maxTextureArrayLayers}.`);
2141
+ }
2142
+ if (!maps ||
2143
+ textureFormat !== maps.format ||
2144
+ textureWidth !== maps.width ||
2145
+ textureHeight !== maps.height ||
2146
+ textureDepth !== maps.depthOrArrayLayers) {
2147
+ maps && maps.destroy();
2148
+ this._maps = this._device.createTexture({
2149
+ format: textureFormat,
2150
+ label: 'Maps',
2151
+ usage: GPUTextureUsage.TEXTURE_BINDING |
2152
+ GPUTextureUsage.COPY_DST |
2153
+ GPUTextureUsage.RENDER_ATTACHMENT,
2154
+ size: {
2155
+ width: textureWidth,
2156
+ height: textureHeight,
2157
+ depthOrArrayLayers: textureDepth
2158
+ }
2159
+ });
2160
+ this._needsUpdateBindGroups = true;
2161
+ }
2162
+ // Upload the textures
2163
+ texture?.source.forEach((img, idx) => {
2164
+ if (isTextureDataSource(img)) {
2165
+ if (!(img.data instanceof Uint8Array) && !(img.data instanceof Uint16Array)) {
2166
+ throw new Error('Material texture atlas GPU upload requires RGBA8 or RGBA16F data.');
2167
+ }
2168
+ this._device.queue.writeTexture({ texture: this._maps, origin: [0, 0, idx] }, img.data, { bytesPerRow: img.width * bytesPerPixel, rowsPerImage: img.height }, { width: img.width, height: img.height, depthOrArrayLayers: 1 });
2169
+ return;
2170
+ }
2171
+ this._device.queue.copyExternalImageToTexture({
2172
+ source: img,
2173
+ // @ts-ignore WebGPU support different flipY for each layer.
2174
+ flipY: texture.flipYArray[idx]
2175
+ }, { texture: this._maps, origin: [0, 0, idx] }, [img.width, img.height]);
2176
+ });
2177
+ }
2178
+ setLights(lights) {
2179
+ const allAnalyticLights = lights.filter((light) => light.type === 'DirectionalLight' ||
2180
+ light.type === 'SphereLight' ||
2181
+ light.type === 'SpotLight' ||
2182
+ light.type === 'RectLight');
2183
+ if (allAnalyticLights.length > FIXED_LIGHT_COUNT) {
2184
+ console.warn(`Too many lights, only the first ${FIXED_LIGHT_COUNT} lights will be used.`);
2185
+ }
2186
+ const analyticLights = allAnalyticLights.slice(0, FIXED_LIGHT_COUNT);
2187
+ const numAnalyticLights = analyticLights.length;
2188
+ const envImage = this._sceneEnvironment.update(lights);
2189
+ this._rtUniform.views.lightsCount[0] = numAnalyticLights;
2190
+ this._setEnvMap(envImage);
2191
+ const lightTexturesChanged = this._rectLightTextures.update(analyticLights);
2192
+ this._analyticLights = analyticLights;
2193
+ if (lightTexturesChanged)
2194
+ this._needsUpdateMeshData = true;
2195
+ this._needsUpdateAnalyticLightsBuffer = true;
2196
+ this._needsUpdateLightSamplingData = true;
2197
+ }
2198
+ _updateViewProjInv(camera, viewProjInv) {
2199
+ mat4.invert(viewProjInv, camera.getProjectionMatrix(this._targetAspect));
2200
+ mat4.mul(viewProjInv, camera.transform, viewProjInv);
2201
+ }
2202
+ // TODO reuse the code of RayTraceNode
2203
+ setCamera(camera) {
2204
+ const viewProjectionInvMat = this._viewProjInv;
2205
+ this._apertureRadius = cameraApertureRadius(camera);
2206
+ this._focusDistance = camera.focusDistance;
2207
+ this._updateViewProjInv(camera, viewProjectionInvMat);
2208
+ mat4.copy(this._cameraWorld, camera.transform);
2209
+ }
2210
+ cameraChanged(camera) {
2211
+ const tmp = mat4.create();
2212
+ this._updateViewProjInv(camera, tmp);
2213
+ return (!numberArraysEqual(this._viewProjInv, tmp) ||
2214
+ cameraApertureRadius(camera) !== this._apertureRadius ||
2215
+ camera.focusDistance !== this._focusDistance);
2216
+ }
2217
+ setSize(width, height, targetAspect = width / height) {
2218
+ this._targetAspect = targetAspect;
2219
+ this._width = width;
2220
+ this._height = height;
2221
+ const arr = this._rtUniform.views.outputSize;
2222
+ arr[0] = width;
2223
+ arr[1] = height;
2224
+ const outColorBuffer = this._outColorBuffer;
2225
+ const bufferSize = width * height * 4 * 4;
2226
+ if (outColorBuffer && outColorBuffer.size === bufferSize) {
2227
+ return;
2228
+ }
2229
+ this._needsUpdateBindGroups = true;
2230
+ const previousBuffers = [];
2231
+ for (let target of [
2232
+ 'Color',
2233
+ 'Normal',
2234
+ 'Albedo',
2235
+ 'ShadowCatcher'
2236
+ ]) {
2237
+ const previous = this[`_out${target}Buffer`];
2238
+ if (previous)
2239
+ previousBuffers.push(previous);
2240
+ this[`_out${target}Buffer`] = this._device.createBuffer({
2241
+ label: 'Ray Trace ' + target,
2242
+ // The catcher stores its mask and its primary background contribution.
2243
+ size: target === 'ShadowCatcher' ? width * height * SHADOW_CATCHER_ACCUMULATION_BYTES : bufferSize,
2244
+ usage: GPUBufferUsage.STORAGE |
2245
+ GPUBufferUsage.COPY_DST |
2246
+ GPUBufferUsage.COPY_SRC
2247
+ });
2248
+ }
2249
+ this._targetOutputs.set(this._activeTarget, { buffers: [this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] });
2250
+ // A preview-resolution update can replace these buffers immediately after
2251
+ // an encoded ray-trace or output command was submitted. Retire every old
2252
+ // output only after the queue has completed work already submitted
2253
+ // against it.
2254
+ void this._device.queue
2255
+ .onSubmittedWorkDone()
2256
+ .catch(() => undefined)
2257
+ .then(() => previousBuffers.forEach((buffer) => buffer.destroy()));
2258
+ }
2259
+ setJitter(x, y) {
2260
+ const arr = this._rtUniform.views.jitter;
2261
+ arr[0] = x;
2262
+ arr[1] = y;
2263
+ this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, new F32([
2264
+ ...this._viewProjInv,
2265
+ ...this._cameraWorld,
2266
+ this._apertureRadius,
2267
+ this._focusDistance,
2268
+ 0,
2269
+ 0
2270
+ ]));
2271
+ }
2272
+ setSampleTiles(columns, rows, tileX, tileY) {
2273
+ const arr = this._rtUniform.views.scaleOffset;
2274
+ arr[0] = columns;
2275
+ arr[1] = rows;
2276
+ arr[2] = tileX;
2277
+ arr[3] = tileY;
2278
+ }
2279
+ setSampleCount(sampleCount) {
2280
+ this._rtUniform.views.sampleCount[0] = sampleCount;
2281
+ }
2282
+ setTransmissiveOutputAlphaMin(transmissiveAlphaMin) {
2283
+ this._rtUniform.views.transmissiveOutputAlphaMin[0] = transmissiveAlphaMin;
2284
+ const changed = this._transmissiveAlphaMin !== transmissiveAlphaMin;
2285
+ this._transmissiveAlphaMin = transmissiveAlphaMin;
2286
+ return changed;
2287
+ }
2288
+ needsDenoiseAlpha() {
2289
+ return (this._hasShadowCatcher ||
2290
+ (this._transmissiveAlphaMin < 1 && this._hasTransmission));
2291
+ }
2292
+ hasShadowCatcher() {
2293
+ return this._hasShadowCatcher;
2294
+ }
2295
+ dispose() {
2296
+ this._shaderCache.clear();
2297
+ const buffers = new Set([
2298
+ ...Array.from(this._targetOutputs.values()).flatMap(target => target.buffers),
2299
+ this._mapInfoUniform,
2300
+ this._attrBuffers,
2301
+ this._materialsBuffer,
2302
+ this._bvhBuffer,
2303
+ this._instancesBuffer,
2304
+ this._offlineAdaptiveHistory?.varianceBuffer,
2305
+ ...Object.values(this._uniforms)
2306
+ ]);
2307
+ buffers.forEach((buffer) => buffer?.destroy());
2308
+ const textures = new Set([
2309
+ this._maps,
2310
+ this._lightSamplingTexture,
2311
+ this._openPbrEnergyLut,
2312
+ this._envTex,
2313
+ this._envDistributionTex,
2314
+ this._backgroundTex,
2315
+ this._adaptiveSamplingTexture
2316
+ ]);
2317
+ textures.forEach((texture) => texture?.destroy());
2318
+ this._rayTracePipeline = undefined;
2319
+ this._rayTraceBindGroups = undefined;
2320
+ }
2321
+ }
2322
+ export default WGPURayTracePass;
2323
+ //# sourceMappingURL=WGPURayTracePass.js.map