@driftengine/core 3.63.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/CHANGELOG.json +111 -0
  2. package/NOTICE +20 -0
  3. package/README.md +1 -1
  4. package/dist/core/loop.js +18 -2
  5. package/dist/core/storage.js +5 -0
  6. package/dist/geometry/meshBuilder.d.ts +1 -1
  7. package/dist/geometry/meshBuilder.js +6 -2
  8. package/dist/index.d.ts +69 -4
  9. package/dist/index.js +112 -2
  10. package/dist/input/input.js +6 -1
  11. package/dist/math/exact.d.ts +13 -3
  12. package/dist/math/exact.js +68 -3
  13. package/dist/math/frustum.d.ts +17 -2
  14. package/dist/math/frustum.js +24 -0
  15. package/dist/render/backend/api.d.ts +34 -1
  16. package/dist/render/backend/api.js +22 -1
  17. package/dist/render/backend/budget.d.ts +7 -6
  18. package/dist/render/backend/budget.js +7 -6
  19. package/dist/render/backend/createRenderer.d.ts +57 -0
  20. package/dist/render/backend/createRenderer.js +51 -2
  21. package/dist/render/backend/materialChanges.d.ts +41 -0
  22. package/dist/render/backend/materialChanges.js +38 -0
  23. package/dist/render/backend/select.js +8 -0
  24. package/dist/render/backend/webgl2/renderer.d.ts +80 -1
  25. package/dist/render/backend/webgl2/renderer.js +208 -10
  26. package/dist/render/backend/webgpu/buffers.d.ts +20 -1
  27. package/dist/render/backend/webgpu/buffers.js +116 -58
  28. package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
  29. package/dist/render/backend/webgpu/fieldCompose.js +396 -0
  30. package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
  31. package/dist/render/backend/webgpu/giFieldPass.js +137 -0
  32. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
  33. package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
  34. package/dist/render/backend/webgpu/linePass.d.ts +4 -14
  35. package/dist/render/backend/webgpu/linePass.js +4 -33
  36. package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
  37. package/dist/render/backend/webgpu/probeBake.js +493 -0
  38. package/dist/render/backend/webgpu/renderer.d.ts +379 -24
  39. package/dist/render/backend/webgpu/renderer.js +1369 -76
  40. package/dist/render/backend/webgpu/scatterPass.js +15 -1
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
  43. package/dist/render/causticsRenderer.d.ts +3 -1
  44. package/dist/render/causticsRenderer.js +4 -1
  45. package/dist/render/depthConvention.d.ts +12 -0
  46. package/dist/render/depthConvention.js +14 -0
  47. package/dist/render/deviceFeatures.d.ts +32 -0
  48. package/dist/render/deviceFeatures.js +41 -0
  49. package/dist/render/fog.d.ts +79 -0
  50. package/dist/render/fog.js +138 -0
  51. package/dist/render/frame/alias.d.ts +30 -0
  52. package/dist/render/frame/alias.js +61 -0
  53. package/dist/render/frame/deps.d.ts +37 -0
  54. package/dist/render/frame/deps.js +122 -0
  55. package/dist/render/frame/flushGraph.d.ts +43 -0
  56. package/dist/render/frame/flushGraph.js +84 -0
  57. package/dist/render/frame/graphSchedule.d.ts +49 -0
  58. package/dist/render/frame/graphSchedule.js +120 -0
  59. package/dist/render/frame/index.d.ts +15 -0
  60. package/dist/render/frame/index.js +8 -0
  61. package/dist/render/frame/lifetime.d.ts +24 -0
  62. package/dist/render/frame/lifetime.js +36 -0
  63. package/dist/render/frame/passTimings.d.ts +42 -0
  64. package/dist/render/frame/passTimings.js +38 -0
  65. package/dist/render/frame/validate.d.ts +15 -0
  66. package/dist/render/frame/validate.js +22 -0
  67. package/dist/render/frame/virtual.d.ts +44 -0
  68. package/dist/render/frame/virtual.js +61 -0
  69. package/dist/render/gi/chain.d.ts +82 -0
  70. package/dist/render/gi/chain.js +135 -0
  71. package/dist/render/gi/denoise.d.ts +114 -0
  72. package/dist/render/gi/denoise.js +169 -0
  73. package/dist/render/gi/fieldScene.d.ts +125 -0
  74. package/dist/render/gi/fieldScene.js +219 -0
  75. package/dist/render/gi/globalField.d.ts +157 -0
  76. package/dist/render/gi/globalField.js +373 -0
  77. package/dist/render/gi/probeGridFit.d.ts +24 -0
  78. package/dist/render/gi/probeGridFit.js +71 -0
  79. package/dist/render/gi/probeTrace.d.ts +82 -0
  80. package/dist/render/gi/probeTrace.js +128 -0
  81. package/dist/render/gi/probeVolume.d.ts +136 -0
  82. package/dist/render/gi/probeVolume.js +191 -0
  83. package/dist/render/gi/reflection.d.ts +73 -0
  84. package/dist/render/gi/reflection.js +109 -0
  85. package/dist/render/gi/traceField.d.ts +91 -0
  86. package/dist/render/gi/traceField.js +129 -0
  87. package/dist/render/gi/traceScreen.d.ts +68 -0
  88. package/dist/render/gi/traceScreen.js +82 -0
  89. package/dist/render/gpuScene.d.ts +38 -0
  90. package/dist/render/gpuScene.js +41 -0
  91. package/dist/render/gpudriven/alphaTest.d.ts +28 -0
  92. package/dist/render/gpudriven/alphaTest.js +32 -0
  93. package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
  94. package/dist/render/gpudriven/clusterUpload.js +75 -0
  95. package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
  96. package/dist/render/gpudriven/clusterWorld.js +155 -0
  97. package/dist/render/gpudriven/compact.d.ts +51 -0
  98. package/dist/render/gpudriven/compact.js +68 -0
  99. package/dist/render/gpudriven/cullClusters.d.ts +129 -0
  100. package/dist/render/gpudriven/cullClusters.js +237 -0
  101. package/dist/render/gpudriven/decodeTables.d.ts +64 -0
  102. package/dist/render/gpudriven/decodeTables.js +219 -0
  103. package/dist/render/gpudriven/frustum.d.ts +25 -0
  104. package/dist/render/gpudriven/frustum.js +59 -0
  105. package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
  106. package/dist/render/gpudriven/geometryHarness.js +31 -0
  107. package/dist/render/gpudriven/hzb.d.ts +60 -0
  108. package/dist/render/gpudriven/hzb.js +97 -0
  109. package/dist/render/gpudriven/ibl.d.ts +85 -0
  110. package/dist/render/gpudriven/ibl.js +129 -0
  111. package/dist/render/gpudriven/indirect.d.ts +67 -0
  112. package/dist/render/gpudriven/indirect.js +77 -0
  113. package/dist/render/gpudriven/instances.d.ts +43 -0
  114. package/dist/render/gpudriven/instances.js +172 -0
  115. package/dist/render/gpudriven/lit.d.ts +165 -0
  116. package/dist/render/gpudriven/lit.js +165 -0
  117. package/dist/render/gpudriven/lodCut.d.ts +28 -0
  118. package/dist/render/gpudriven/lodCut.js +38 -0
  119. package/dist/render/gpudriven/materialBin.d.ts +60 -0
  120. package/dist/render/gpudriven/materialBin.js +108 -0
  121. package/dist/render/gpudriven/materialTable.d.ts +88 -0
  122. package/dist/render/gpudriven/materialTable.js +115 -0
  123. package/dist/render/gpudriven/pipeline.d.ts +135 -0
  124. package/dist/render/gpudriven/pipeline.js +237 -0
  125. package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
  126. package/dist/render/gpudriven/rangeAlloc.js +112 -0
  127. package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
  128. package/dist/render/gpudriven/sceneUpload.js +115 -0
  129. package/dist/render/gpudriven/shadeBins.d.ts +92 -0
  130. package/dist/render/gpudriven/shadeBins.js +182 -0
  131. package/dist/render/gpudriven/shadow.d.ts +136 -0
  132. package/dist/render/gpudriven/shadow.js +224 -0
  133. package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
  134. package/dist/render/gpudriven/shadowCamera.js +345 -0
  135. package/dist/render/gpudriven/streamScene.d.ts +194 -0
  136. package/dist/render/gpudriven/streamScene.js +459 -0
  137. package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
  138. package/dist/render/gpudriven/surfaceFrame.js +51 -0
  139. package/dist/render/gpudriven/twoPhase.d.ts +122 -0
  140. package/dist/render/gpudriven/twoPhase.js +160 -0
  141. package/dist/render/gpudriven/visbuffer.d.ts +25 -0
  142. package/dist/render/gpudriven/visbuffer.js +37 -0
  143. package/dist/render/halfFloat.d.ts +10 -0
  144. package/dist/render/halfFloat.js +23 -4
  145. package/dist/render/inference/attentionTiled.d.ts +27 -0
  146. package/dist/render/inference/attentionTiled.js +117 -0
  147. package/dist/render/inference/denseKernels.d.ts +29 -0
  148. package/dist/render/inference/denseKernels.js +312 -0
  149. package/dist/render/inference/device.d.ts +21 -0
  150. package/dist/render/inference/device.js +32 -0
  151. package/dist/render/inference/deviceGraph.d.ts +34 -0
  152. package/dist/render/inference/deviceGraph.js +17 -0
  153. package/dist/render/inference/fuse.d.ts +15 -0
  154. package/dist/render/inference/fuse.js +37 -0
  155. package/dist/render/inference/kernelKit.d.ts +52 -0
  156. package/dist/render/inference/kernelKit.js +70 -0
  157. package/dist/render/inference/kernels.d.ts +3 -0
  158. package/dist/render/inference/kernels.js +11 -0
  159. package/dist/render/inference/plan.d.ts +13 -0
  160. package/dist/render/inference/plan.js +18 -0
  161. package/dist/render/inference/reuse.d.ts +39 -0
  162. package/dist/render/inference/reuse.js +65 -0
  163. package/dist/render/inference/runner.d.ts +31 -0
  164. package/dist/render/inference/runner.js +175 -0
  165. package/dist/render/inference/schedule.d.ts +39 -0
  166. package/dist/render/inference/schedule.js +59 -0
  167. package/dist/render/inference/shapeKernels.d.ts +10 -0
  168. package/dist/render/inference/shapeKernels.js +80 -0
  169. package/dist/render/inference/spatialKernels.d.ts +21 -0
  170. package/dist/render/inference/spatialKernels.js +179 -0
  171. package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
  172. package/dist/render/inference/splatRaster.wgsl.js +337 -0
  173. package/dist/render/inference/splatTiles.d.ts +79 -0
  174. package/dist/render/inference/splatTiles.js +161 -0
  175. package/dist/render/lineBatch.d.ts +2 -5
  176. package/dist/render/lineBatch.js +4 -24
  177. package/dist/render/mesh.d.ts +4 -0
  178. package/dist/render/mesh.js +45 -1
  179. package/dist/render/pass.d.ts +145 -8
  180. package/dist/render/prefilterEnvMap.d.ts +22 -0
  181. package/dist/render/prefilterEnvMap.js +31 -0
  182. package/dist/render/readback.d.ts +30 -0
  183. package/dist/render/readback.js +47 -0
  184. package/dist/render/recon/clamp.d.ts +36 -0
  185. package/dist/render/recon/clamp.js +92 -0
  186. package/dist/render/recon/disocclusion.d.ts +60 -0
  187. package/dist/render/recon/disocclusion.js +109 -0
  188. package/dist/render/recon/frameGen.d.ts +81 -0
  189. package/dist/render/recon/frameGen.js +150 -0
  190. package/dist/render/recon/frameSizes.d.ts +34 -0
  191. package/dist/render/recon/frameSizes.js +58 -0
  192. package/dist/render/recon/jitter.d.ts +60 -0
  193. package/dist/render/recon/jitter.js +118 -0
  194. package/dist/render/recon/motionVectors.d.ts +55 -0
  195. package/dist/render/recon/motionVectors.js +107 -0
  196. package/dist/render/recon/reproject.d.ts +41 -0
  197. package/dist/render/recon/reproject.js +131 -0
  198. package/dist/render/recon/resolve.d.ts +90 -0
  199. package/dist/render/recon/resolve.js +313 -0
  200. package/dist/render/recon/tier.d.ts +24 -0
  201. package/dist/render/recon/tier.js +8 -0
  202. package/dist/render/reflectionProbe.d.ts +26 -0
  203. package/dist/render/reflectionProbe.js +33 -0
  204. package/dist/render/renderQuality.d.ts +69 -0
  205. package/dist/render/renderQuality.js +22 -0
  206. package/dist/render/screenSpaceReflection.d.ts +23 -0
  207. package/dist/render/screenSpaceReflection.js +22 -3
  208. package/dist/render/sdfFont.js +2 -0
  209. package/dist/render/segmentQuads.d.ts +13 -0
  210. package/dist/render/segmentQuads.js +32 -0
  211. package/dist/render/shaders/flat/lobes.d.ts +27 -0
  212. package/dist/render/shaders/flat/lobes.js +33 -3
  213. package/dist/render/shaders/flat/preamble.d.ts +7 -0
  214. package/dist/render/shaders/flat/preamble.js +24 -1
  215. package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
  216. package/dist/render/shaders/flat/probeGrid.js +71 -2
  217. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
  218. package/dist/render/shaders/generated/flat.wgsl.js +64 -24
  219. package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
  220. package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
  221. package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
  222. package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
  223. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
  224. package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
  225. package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
  226. package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
  227. package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
  228. package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
  229. package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
  230. package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
  231. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
  232. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
  233. package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
  234. package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
  235. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
  236. package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
  237. package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
  238. package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
  239. package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
  240. package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
  241. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
  242. package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
  243. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
  244. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
  245. package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
  246. package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
  247. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
  248. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
  249. package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
  250. package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
  251. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
  252. package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
  253. package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
  254. package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
  255. package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
  256. package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
  257. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
  258. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
  259. package/dist/render/shaders/network.wgsl.d.ts +101 -0
  260. package/dist/render/shaders/network.wgsl.js +206 -0
  261. package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
  262. package/dist/render/shaders/recon/motion.wgsl.js +96 -0
  263. package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
  264. package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
  265. package/dist/render/temporalAa.js +8 -33
  266. package/dist/render/textLayout.js +12 -6
  267. package/dist/render/textRenderer.d.ts +3 -1
  268. package/dist/render/textRenderer.js +4 -1
  269. package/dist/render/translucentQueue.d.ts +6 -0
  270. package/dist/render/translucentQueue.js +11 -0
  271. package/dist/render/windStreakRenderer.d.ts +2 -1
  272. package/dist/render/windStreakRenderer.js +3 -1
  273. package/dist/world/cell.d.ts +63 -0
  274. package/dist/world/cell.js +181 -0
  275. package/dist/world/cellStream.d.ts +103 -0
  276. package/dist/world/cellStream.js +198 -0
  277. package/dist/world/freeze.d.ts +81 -0
  278. package/dist/world/freeze.js +114 -0
  279. package/dist/world/rebase.d.ts +29 -0
  280. package/dist/world/rebase.js +41 -0
  281. package/package.json +8 -3
  282. package/scripts/frames.mjs +92 -0
  283. package/scripts/gpuCompute.mjs +323 -0
  284. package/scripts/png.d.mts +16 -0
  285. package/scripts/png.mjs +25 -0
  286. package/src/core/loop.ts +19 -2
  287. package/src/core/storage.ts +5 -0
  288. package/src/geometry/meshBuilder.ts +5 -1
  289. package/src/index.ts +246 -4
  290. package/src/input/input.ts +6 -1
  291. package/src/math/exact.ts +69 -3
  292. package/src/math/frustum.ts +52 -3
  293. package/src/render/backend/api.ts +42 -1
  294. package/src/render/backend/budget.ts +7 -6
  295. package/src/render/backend/createRenderer.ts +92 -3
  296. package/src/render/backend/materialChanges.ts +54 -0
  297. package/src/render/backend/select.ts +8 -0
  298. package/src/render/backend/webgl2/renderer.ts +211 -9
  299. package/src/render/backend/webgpu/buffers.ts +182 -69
  300. package/src/render/backend/webgpu/fieldCompose.ts +482 -0
  301. package/src/render/backend/webgpu/giFieldPass.ts +200 -0
  302. package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
  303. package/src/render/backend/webgpu/linePass.ts +4 -39
  304. package/src/render/backend/webgpu/probeBake.ts +588 -0
  305. package/src/render/backend/webgpu/renderer.ts +1481 -76
  306. package/src/render/backend/webgpu/scatterPass.ts +15 -1
  307. package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
  308. package/src/render/causticsRenderer.ts +5 -2
  309. package/src/render/depthConvention.ts +15 -0
  310. package/src/render/deviceFeatures.ts +77 -0
  311. package/src/render/fog.ts +185 -0
  312. package/src/render/frame/alias.ts +97 -0
  313. package/src/render/frame/deps.ts +155 -0
  314. package/src/render/frame/flushGraph.ts +135 -0
  315. package/src/render/frame/graphSchedule.ts +170 -0
  316. package/src/render/frame/index.ts +52 -0
  317. package/src/render/frame/lifetime.ts +61 -0
  318. package/src/render/frame/passTimings.ts +69 -0
  319. package/src/render/frame/validate.ts +40 -0
  320. package/src/render/frame/virtual.ts +91 -0
  321. package/src/render/gi/chain.ts +225 -0
  322. package/src/render/gi/denoise.ts +276 -0
  323. package/src/render/gi/fieldScene.ts +264 -0
  324. package/src/render/gi/globalField.ts +543 -0
  325. package/src/render/gi/probeGridFit.ts +89 -0
  326. package/src/render/gi/probeTrace.ts +143 -0
  327. package/src/render/gi/probeVolume.ts +288 -0
  328. package/src/render/gi/reflection.ts +163 -0
  329. package/src/render/gi/traceField.ts +223 -0
  330. package/src/render/gi/traceScreen.ts +106 -0
  331. package/src/render/gpuScene.ts +80 -0
  332. package/src/render/gpudriven/alphaTest.ts +33 -0
  333. package/src/render/gpudriven/clusterUpload.ts +123 -0
  334. package/src/render/gpudriven/clusterWorld.ts +239 -0
  335. package/src/render/gpudriven/compact.ts +80 -0
  336. package/src/render/gpudriven/cullClusters.ts +333 -0
  337. package/src/render/gpudriven/decodeTables.ts +332 -0
  338. package/src/render/gpudriven/frustum.ts +70 -0
  339. package/src/render/gpudriven/geometryHarness.ts +51 -0
  340. package/src/render/gpudriven/hzb.ts +113 -0
  341. package/src/render/gpudriven/ibl.ts +147 -0
  342. package/src/render/gpudriven/indirect.ts +89 -0
  343. package/src/render/gpudriven/instances.ts +234 -0
  344. package/src/render/gpudriven/lit.ts +329 -0
  345. package/src/render/gpudriven/lodCut.ts +62 -0
  346. package/src/render/gpudriven/materialBin.ts +131 -0
  347. package/src/render/gpudriven/materialTable.ts +164 -0
  348. package/src/render/gpudriven/pipeline.ts +336 -0
  349. package/src/render/gpudriven/rangeAlloc.ts +122 -0
  350. package/src/render/gpudriven/sceneUpload.ts +173 -0
  351. package/src/render/gpudriven/shadeBins.ts +222 -0
  352. package/src/render/gpudriven/shadow.ts +285 -0
  353. package/src/render/gpudriven/shadowCamera.ts +406 -0
  354. package/src/render/gpudriven/streamScene.ts +575 -0
  355. package/src/render/gpudriven/surfaceFrame.ts +68 -0
  356. package/src/render/gpudriven/twoPhase.ts +214 -0
  357. package/src/render/gpudriven/visbuffer.ts +41 -0
  358. package/src/render/halfFloat.ts +22 -4
  359. package/src/render/inference/attentionTiled.ts +132 -0
  360. package/src/render/inference/denseKernels.ts +369 -0
  361. package/src/render/inference/device.ts +44 -0
  362. package/src/render/inference/deviceGraph.ts +39 -0
  363. package/src/render/inference/fuse.ts +47 -0
  364. package/src/render/inference/kernelKit.ts +120 -0
  365. package/src/render/inference/kernels.ts +18 -0
  366. package/src/render/inference/plan.ts +26 -0
  367. package/src/render/inference/reuse.ts +79 -0
  368. package/src/render/inference/runner.ts +204 -0
  369. package/src/render/inference/schedule.ts +80 -0
  370. package/src/render/inference/shapeKernels.ts +112 -0
  371. package/src/render/inference/spatialKernels.ts +220 -0
  372. package/src/render/inference/splatRaster.wgsl.ts +348 -0
  373. package/src/render/inference/splatTiles.ts +186 -0
  374. package/src/render/lineBatch.ts +4 -24
  375. package/src/render/mesh.ts +46 -1
  376. package/src/render/pass.ts +155 -10
  377. package/src/render/prefilterEnvMap.ts +33 -0
  378. package/src/render/readback.ts +73 -0
  379. package/src/render/recon/clamp.ts +113 -0
  380. package/src/render/recon/disocclusion.ts +143 -0
  381. package/src/render/recon/frameGen.ts +191 -0
  382. package/src/render/recon/frameSizes.ts +64 -0
  383. package/src/render/recon/jitter.ts +126 -0
  384. package/src/render/recon/motionVectors.ts +138 -0
  385. package/src/render/recon/reproject.ts +179 -0
  386. package/src/render/recon/resolve.ts +444 -0
  387. package/src/render/recon/tier.ts +31 -0
  388. package/src/render/reflectionProbe.ts +34 -0
  389. package/src/render/renderQuality.ts +87 -0
  390. package/src/render/screenSpaceReflection.ts +37 -3
  391. package/src/render/sdfFont.ts +2 -0
  392. package/src/render/segmentQuads.ts +39 -0
  393. package/src/render/shaders/flat/lobes.ts +35 -3
  394. package/src/render/shaders/flat/preamble.ts +25 -1
  395. package/src/render/shaders/flat/probeGrid.ts +77 -2
  396. package/src/render/shaders/generated/flat.wgsl.ts +64 -24
  397. package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
  398. package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
  399. package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
  400. package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
  401. package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
  402. package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
  403. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
  404. package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
  405. package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
  406. package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
  407. package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
  408. package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
  409. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
  410. package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
  411. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
  412. package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
  413. package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
  414. package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
  415. package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
  416. package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
  417. package/src/render/shaders/network.wgsl.ts +238 -0
  418. package/src/render/shaders/recon/motion.wgsl.ts +101 -0
  419. package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
  420. package/src/render/temporalAa.ts +9 -33
  421. package/src/render/textLayout.ts +12 -6
  422. package/src/render/textRenderer.ts +5 -2
  423. package/src/render/translucentQueue.ts +11 -0
  424. package/src/render/windStreakRenderer.ts +4 -2
  425. package/src/world/cell.ts +231 -0
  426. package/src/world/cellStream.ts +278 -0
  427. package/src/world/freeze.ts +157 -0
  428. package/src/world/rebase.ts +56 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * A probe's own rays, on the device: `gi/chain.ts` minus its screen level.
3
+ *
4
+ * **A probe has no use for screen space, and including it would be a defect rather than a cost.**
5
+ * The screen level is an accelerator for rays cast from the camera's own view, where the frame
6
+ * already holds the answer. A probe stands in the world and its rays go everywhere, so most of them
7
+ * leave the frame — and the few that do not would make a *cached* probe depend on where the camera
8
+ * happens to be, so a probe would change its contents as the viewer turned and every surface it
9
+ * lights would shimmer. So the chain is entered with a projector that answers "off screen" for
10
+ * every point, which makes level 0 contribute exactly nothing and levels 1 and 2 take the whole
11
+ * ray. The reference stays one function; this is that function with one input pinned.
12
+ *
13
+ * **What a field hit is worth comes from the probes**, which is the maintainer's answer of
14
+ * 2026-09-17 and the reason `GiResources.fieldRadiance` is a function rather than a texture: a
15
+ * probe traced against radiance that is itself probe radiance converges on several bounces over a
16
+ * few refreshes. The direction it is read along is back down the ray — the light leaving that
17
+ * surface *towards* the probe is what the probe is measuring — which the chain's own signature
18
+ * leaves to the caller because it takes no direction.
19
+ *
20
+ * **One core, two modules**, as `shaders/recon/resolve.wgsl.ts` and `network.wgsl.ts` are. The
21
+ * arithmetic reads the field and the probes through functions the including module defines, so the
22
+ * production module can read the renderer's own buffers and the parity check can read plain ones of
23
+ * the same numbers.
24
+ *
25
+ * The accessors an including module must define, before including the core:
26
+ *
27
+ * ```wgsl
28
+ * fn probeFieldAt(p: vec3<f32>) -> f32 // the composed field's distance at a world point
29
+ * fn probeFieldInside(p: vec3<f32>) -> f32 // how far inside the outermost cascade, negative outside
30
+ * fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
31
+ * fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
32
+ * ```
33
+ *
34
+ * **Two, and it used to be one.** A ray that struck a wall and a ray that left the world need
35
+ * different answers and the single accessor could not tell them apart: what leaves a wall is its
36
+ * albedo times the light reaching it, and what a ray finds having left is whatever the probe
37
+ * volume coarsely says is out there. Measured with one accessor, a red wall and a white wall
38
+ * returned the same colour — `demo/dev/bounce.html`, and `plans/2026-09-17-indirect-light.md`
39
+ * Task 8.
40
+ *
41
+ * and `march: ProbeMarch` as a uniform.
42
+ */
43
+
44
+ import { GLOBAL_FIELD_BLEND } from '../../gi/globalField.ts';
45
+ import { FIELD_MARCH, FIELD_START_VOXELS } from '../../gi/traceField.ts';
46
+ import { SAMPLE_FIELD_WGSL } from './sampleField.wgsl.ts';
47
+
48
+ /**
49
+ * Floats in the march block, which is sixteen because of how a uniform aligns rather than how many
50
+ * numbers it holds: four counts, four lengths, and two `vec3`s that each take a whole four-float row.
51
+ */
52
+ export const PROBE_MARCH_FLOATS = 16;
53
+
54
+ /** The workgroup a trace dispatches over: one invocation a direction. */
55
+ export const PROBE_TRACE_WORKGROUP = 64;
56
+
57
+ /** A number as WGSL always reads it as a float, so an integer constant is not an `i32`. */
58
+ function f(value: number): string {
59
+ return Number.isInteger(value) ? value.toFixed(1) : String(value);
60
+ }
61
+
62
+ export const PROBE_MARCH_WGSL = /* wgsl */ `
63
+ struct ProbeMarch {
64
+ /* Steps a ray may take, the cascades it may read, and how many samples a cascade is a side. */
65
+ steps: u32,
66
+ levels: u32,
67
+ side: u32,
68
+ rayCount: u32,
69
+ /* How far a ray may travel, how close to a surface counts as arriving, the finest voxel. */
70
+ reachM: f32,
71
+ hitEpsilonM: f32,
72
+ finestStep: f32,
73
+ coneAngle: f32,
74
+ /* The outermost cascade's corners, for the confidence the last level is faded by. */
75
+ outerLow: vec3<f32>,
76
+ outerHigh: vec3<f32>,
77
+ }
78
+ `;
79
+
80
+ /**
81
+ * The march and the two levels, as text.
82
+ *
83
+ * Defines `probeIndirect(point, normal, direction, coneAngle) -> vec3<f32>`, which is what one of a
84
+ * probe's rays is worth.
85
+ */
86
+ export const PROBE_TRACE_CORE_WGSL = /* wgsl */ `
87
+ const PROBE_FIELD_START_VOXELS: f32 = ${f(FIELD_START_VOXELS)};
88
+ const PROBE_FIELD_BLEND: f32 = ${f(GLOBAL_FIELD_BLEND)};
89
+
90
+ struct ProbeFieldHit {
91
+ hit: bool,
92
+ point: vec3<f32>,
93
+ }
94
+
95
+ /* A cone's radius at a distance. Zero angle is a ray, which is what a probe's rays are. */
96
+ fn probeConeRadius(distanceM: f32, coneAngle: f32) -> f32 {
97
+ return select(0.0, distanceM * tan(coneAngle), coneAngle > 0.0);
98
+ }
99
+
100
+ /*
101
+ * March one ray through the composed world field.
102
+ *
103
+ * **Lifted along the normal and only along the normal.** Pushing the start along the ray as well
104
+ * double-counts: a ray leaving along its own normal would begin two offsets out and every distance
105
+ * it reported would carry the second one. And it does not help the case that needs help — a tangent
106
+ * ray pushed along itself is still on the surface, because a surface is what tangent means.
107
+ */
108
+ fn probeTraceField(
109
+ point: vec3<f32>,
110
+ normal: vec3<f32>,
111
+ direction: vec3<f32>,
112
+ coneAngle: f32,
113
+ ) -> ProbeFieldHit {
114
+ var out: ProbeFieldHit;
115
+ out.hit = false;
116
+ out.point = point;
117
+
118
+ /* Not named "length": a local of that name shadows the built-in and the next call to it fails. */
119
+ let reach = length(direction);
120
+ if (!(reach > 0.0)) { return out; }
121
+ let unit = direction / reach;
122
+
123
+ let start = PROBE_FIELD_START_VOXELS * march.finestStep;
124
+ let normalLength = length(normal);
125
+ let lifted = point + select(vec3<f32>(0.0), normal * (start / normalLength), normalLength > 0.0);
126
+
127
+ var travelled = 0.0;
128
+ for (var step = 0u; step < march.steps; step = step + 1u) {
129
+ let at = lifted + unit * travelled;
130
+ /*
131
+ * **Outside the outermost cascade the march stops rather than sampling.** There is no
132
+ * information out there: the sampler clamps to the edge, which is right for a sample and wrong
133
+ * for a march — stepping on clamped values converges on the cascade's boundary and reports a
134
+ * surface made of nothing.
135
+ */
136
+ if (probeFieldInside(at) <= 0.0) { return out; }
137
+
138
+ let distance = probeFieldAt(at);
139
+ if (distance <= probeConeRadius(travelled, coneAngle) + march.hitEpsilonM) {
140
+ out.hit = true;
141
+ out.point = at;
142
+ return out;
143
+ }
144
+ /*
145
+ * **Step by the distance, and by at least a quarter of a voxel.** A field whose value is
146
+ * genuinely tiny beside a surface the cone is not wide enough to accept would otherwise take
147
+ * ever-smaller steps and spend the whole budget arriving nowhere, which reads as a miss and
148
+ * costs a fallback anyway. The floor makes that case terminate quickly instead.
149
+ */
150
+ travelled = travelled + max(distance, march.finestStep * 0.25);
151
+ if (travelled > march.reachM) { return out; }
152
+ }
153
+ return out;
154
+ }
155
+
156
+ /*
157
+ * How much of a field hit is trusted, faded across the outer tenth of the outermost cascade.
158
+ *
159
+ * Every boundary in this chain moves with the camera — the cascades are centred on it — and a hard
160
+ * switch at a boundary that moves is a seam sweeping through the picture whenever the camera turns.
161
+ */
162
+ fn probeFieldConfidence(p: vec3<f32>) -> f32 {
163
+ let band = PROBE_FIELD_BLEND * (march.outerHigh.x - march.outerLow.x);
164
+ if (band <= 0.0) { return 1.0; }
165
+ let gap = min(p - march.outerLow, march.outerHigh - p);
166
+ return clamp(min(gap.x, min(gap.y, gap.z)) / band, 0.0, 1.0);
167
+ }
168
+
169
+ /*
170
+ * One of a probe's rays, as radiance.
171
+ *
172
+ * Level 1 answers as much as its distance from the outermost cascade's face allows; level 2 takes
173
+ * whatever is left without asking, which is what "never wrong and only ever coarse" buys. There is
174
+ * no case where the probes decline, and no fourth level to fall to.
175
+ */
176
+ fn probeIndirect(
177
+ point: vec3<f32>,
178
+ normal: vec3<f32>,
179
+ direction: vec3<f32>,
180
+ coneAngle: f32,
181
+ ) -> vec3<f32> {
182
+ var radiance = vec3<f32>(0.0);
183
+ var fieldShare = 0.0;
184
+
185
+ let hit = probeTraceField(point, normal, direction, coneAngle);
186
+ if (hit.hit) {
187
+ fieldShare = probeFieldConfidence(hit.point);
188
+ if (fieldShare > 0.0) {
189
+ /* Back down the ray: what this surface sends *towards* the probe is what the probe measures. */
190
+ radiance = radiance + probeSurfaceRadiance(hit.point, -direction) * fieldShare;
191
+ }
192
+ }
193
+
194
+ let probes = 1.0 - fieldShare;
195
+ if (probes > 0.0) {
196
+ radiance = radiance + probeDistantRadiance(point + direction * march.reachM, -direction) * probes;
197
+ }
198
+ return radiance;
199
+ }
200
+ `;
201
+
202
+ /** `FIELD_MARCH`'s own numbers, so a caller fills the block from one place. */
203
+ export const PROBE_MARCH_DEFAULTS = FIELD_MARCH;
204
+
205
+ /**
206
+ * The parity module: the core over plain buffers, with a radiance that is a function of position.
207
+ *
208
+ * **The radiance is positional on purpose.** A march that lands a voxel away from where the
209
+ * reference landed would return almost the right answer from a smooth field and exactly the right
210
+ * one from a constant, so a constant radiance would check the *blend* and nothing about the march.
211
+ * A steep function of the hit's position turns a wrong hit into a wrong colour.
212
+ *
213
+ * `probeRadianceAt` is an accessor by design, so this check is of the march and the level blend
214
+ * alone; what a probe volume answers is the production module's business and `probeVolume.test.ts`
215
+ * is what holds that.
216
+ */
217
+ export function probeTraceParityWgsl(): string {
218
+ return /* wgsl */ `
219
+ @group(0) @binding(0) var<storage, read> cascades: array<f32>;
220
+ @group(0) @binding(1) var<storage, read> fields: array<f32>;
221
+ ${PROBE_MARCH_WGSL}
222
+ @group(0) @binding(2) var<uniform> march: ProbeMarch;
223
+ /* Seven floats a ray: the point, the normal, the direction. The cone angle is the march's. */
224
+ @group(0) @binding(3) var<storage, read> rays: array<f32>;
225
+ @group(0) @binding(4) var<storage, read_write> radiance: array<f32>;
226
+ ${SAMPLE_FIELD_WGSL}
227
+
228
+ fn probeFieldAt(p: vec3<f32>) -> f32 {
229
+ return sampleField(march.levels, march.side, p);
230
+ }
231
+
232
+ fn probeFieldInside(p: vec3<f32>) -> f32 {
233
+ return insideBy(march.levels - 1u, p);
234
+ }
235
+
236
+ /* The same expression the harness evaluates, and the reason it is a sine of position. */
237
+ fn probeParityRadiance(p: vec3<f32>) -> vec3<f32> {
238
+ return vec3<f32>(
239
+ 0.5 + 0.4 * sin(p.x * 1.3),
240
+ 0.5 + 0.4 * sin(p.y * 1.7 + 1.0),
241
+ 0.5 + 0.4 * sin(p.z * 2.3 + 2.0),
242
+ );
243
+ }
244
+
245
+ /* One expression for both, so this module keeps checking the march and the blend and nothing else. */
246
+ fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
247
+ return probeParityRadiance(p);
248
+ }
249
+
250
+ fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
251
+ return probeParityRadiance(p);
252
+ }
253
+ ${PROBE_TRACE_CORE_WGSL}
254
+
255
+ @compute @workgroup_size(${PROBE_TRACE_WORKGROUP})
256
+ fn traceMain(@builtin(global_invocation_id) id: vec3<u32>) {
257
+ if (id.x >= march.rayCount) { return; }
258
+ let at = id.x * 9u;
259
+ let point = vec3<f32>(rays[at], rays[at + 1u], rays[at + 2u]);
260
+ let normal = vec3<f32>(rays[at + 3u], rays[at + 4u], rays[at + 5u]);
261
+ let direction = vec3<f32>(rays[at + 6u], rays[at + 7u], rays[at + 8u]);
262
+ let value = probeIndirect(point, normal, direction, march.coneAngle);
263
+ let out = id.x * 3u;
264
+ radiance[out] = value.x;
265
+ radiance[out + 1u] = value.y;
266
+ radiance[out + 2u] = value.z;
267
+ }
268
+ `;
269
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * What the probe grid answers at a world point, on the device.
3
+ *
4
+ * **This is the fourth spelling of one idea and the file says so rather than hoping.**
5
+ * `probeGrid.ts` holds the eight-probe trilinear blend in TypeScript, `octahedral.ts` holds the
6
+ * mapping in TypeScript and again in GLSL, and `shaders/flat/probeGrid.ts` holds both together in
7
+ * the GLSL the lit pass uses. This is WGSL, because a hand-written compute shader cannot include a
8
+ * generated one — and `scripts/probe-bake-parity.mjs` is what holds it to the others rather than
9
+ * a reader's confidence that four copies of an expression say the same thing.
10
+ *
11
+ * **Why a probe reads the grid at all**: the maintainer's answer of 2026-09-17 is that what a field
12
+ * hit is worth comes from the probes, so a probe traced against radiance that is itself probe
13
+ * radiance converges on several bounces over a few refreshes. That is what makes this the multi-
14
+ * bounce half of Wave 4A rather than a single-bounce approximation of it.
15
+ *
16
+ * **No visibility term here, deliberately.** `visibleProbes` exists to stop light reaching a
17
+ * *surface* through a wall it is behind, and it is what `gridIrradiance` applies when shading. A
18
+ * probe asking "what is the radiance out there" is a different question with a different failure:
19
+ * crushing a blind probe's weight would make the bounce darker than the room, and the reference
20
+ * this is checked against — `indirect-parity.mjs` — enters the chain with `visibility: null` for
21
+ * exactly this reason.
22
+ *
23
+ * The accessor an including module must define, before including the core:
24
+ *
25
+ * ```wgsl
26
+ * fn probeLayerSample(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the radiance level
27
+ * fn probeLayerIrradiance(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the irradiance level
28
+ * ```
29
+ *
30
+ * and `volume: ProbeVolume` as a uniform. Production samples a texture; the parity module reads a
31
+ * plain buffer of the same numbers.
32
+ */
33
+
34
+ /**
35
+ * Floats in the volume block, and where each field starts.
36
+ *
37
+ * Named offsets for the reason `probeBake.wgsl.ts` gives and paid for: an `f32` after a `vec3<f32>`
38
+ * fills the vec3's padding rather than starting a new row, so `edge` is float **11**.
39
+ */
40
+ export const VOLUME_ORIGIN = 0;
41
+ export const VOLUME_INV_SPACING = 4;
42
+ export const VOLUME_COUNTS = 8;
43
+ export const VOLUME_EDGE = 11;
44
+ export const VOLUME_LEVEL = 12;
45
+ export const VOLUME_RADIANCE_EDGE = 13;
46
+ export const VOLUME_RADIANCE_LEVEL = 14;
47
+
48
+ /** Fifty-two bytes of fields rounded up to the struct's own sixteen-byte alignment. */
49
+ export const PROBE_VOLUME_FLOATS = 16;
50
+
51
+ export const PROBE_VOLUME_STRUCT_WGSL = /* wgsl */ `
52
+ struct ProbeVolume {
53
+ /* Where the grid's first probe stands, and one over the step between them. */
54
+ origin: vec3<f32>,
55
+ invSpacing: vec3<f32>,
56
+ /* How many probes along each axis. Whole numbers carried as floats. */
57
+ counts: vec3<f32>,
58
+ /* Texels across the level being sampled, gutter included. */
59
+ edge: f32,
60
+ /* Which mip of the probe array that level is. */
61
+ level: f32,
62
+ /* And the level that holds *radiance* rather than irradiance, with its own edge. */
63
+ radianceEdge: f32,
64
+ radianceLevel: f32,
65
+ padA: f32,
66
+ }
67
+ `;
68
+
69
+ export const PROBE_VOLUME_CORE_WGSL = /* wgsl */ `
70
+ /* The octahedral encode, matching "octEncode" in "shaders/octahedral.ts". */
71
+ fn probeOctEncode(d: vec3<f32>) -> vec2<f32> {
72
+ let n = d / (abs(d.x) + abs(d.y) + abs(d.z));
73
+ var p = n.xy;
74
+ if (n.z < 0.0) {
75
+ p = (1.0 - abs(vec2<f32>(n.y, n.x))) *
76
+ vec2<f32>(select(-1.0, 1.0, n.x >= 0.0), select(-1.0, 1.0, n.y >= 0.0));
77
+ }
78
+ return p * 0.5 + 0.5;
79
+ }
80
+
81
+ /*
82
+ * Where to sample a direction in a map whose outer ring is a gutter.
83
+ *
84
+ * The inner region is "edge - 2" texels offset by one, so a direction at the very border of the
85
+ * octahedral square lands on the last real texel and the tap that interpolates past it reads the
86
+ * gutter — which the bake filled with the folded direction.
87
+ */
88
+ fn probeOctInsetUv(d: vec3<f32>, edge: f32) -> vec2<f32> {
89
+ return (probeOctEncode(d) * (edge - 2.0) + 1.0) / edge;
90
+ }
91
+
92
+ /*
93
+ * One axis of the lookup: which probe is below the point, and how far past it the point is.
94
+ *
95
+ * **The index is integer arithmetic and the fraction is not**, which is the distinction row 139 of
96
+ * the resume file is about. A world position divided by a spacing is a genuine float division and
97
+ * its floor is a genuine floor; what must never be written in floats is a *lattice* — an index
98
+ * decomposed out of a layer number — because a device may implement that division as a
99
+ * multiplication by a reciprocal and answer 3 for 3.0 % 3.0.
100
+ *
101
+ * The lower index stops one short of the last probe so the pair "index" and "index + 1" is always
102
+ * inside the grid, and a single-probe axis has no pair at all — both corners are probe zero and
103
+ * the fraction is zero, so that axis multiplies every weight by one.
104
+ */
105
+ struct ProbeCell {
106
+ index: u32,
107
+ fraction: f32,
108
+ }
109
+
110
+ fn probeCell(value: f32, origin: f32, invSpacing: f32, count: u32) -> ProbeCell {
111
+ var out: ProbeCell;
112
+ out.index = 0u;
113
+ out.fraction = 0.0;
114
+ if (count <= 1u) { return out; }
115
+ let local = (value - origin) * invSpacing;
116
+ let span = f32(count - 1u);
117
+ let clamped = clamp(local, 0.0, span);
118
+ let index = min(u32(floor(clamped)), count - 2u);
119
+ out.index = index;
120
+ out.fraction = clamped - f32(index);
121
+ return out;
122
+ }
123
+
124
+ /*
125
+ * The layer a probe at whole lattice coordinates occupies. x fastest, then y, then z.
126
+ *
127
+ * **Not clamped, and "ProbeGrid.layerAt" is** — the difference is who may call them. "layerAt" is a
128
+ * public method whose contract is that it accepts anything, so it clamps; this is reached from one
129
+ * place, under two guarantees that together make the clamp unreachable. "probeCell" returns an
130
+ * index no greater than "count - 2", so "index + 1" is inside; and on an axis only one probe wide
131
+ * it returns index 0 with fraction 0, so the far corner's weight is zero and the caller skips it
132
+ * before asking. A clamp here would be a line no perturbation can make fail, which this session
133
+ * has already had to remove twice.
134
+ */
135
+ fn probeLayerAt(ix: u32, iy: u32, iz: u32, counts: vec3<u32>) -> u32 {
136
+ return ix + counts.x * (iy + counts.y * iz);
137
+ }
138
+
139
+ /*
140
+ * What has already bounced onto a surface facing the normal at a point, from the level this writes.
141
+ *
142
+ * **One shared direction, and that is right here where a per-corner one is right for radiance.**
143
+ * An irradiance map is indexed by the *normal of the surface being lit*, not by where the light
144
+ * came from, so every probe is asked the same question: what reaches a surface facing this way.
145
+ * Reading it at the level the bake writes is what closes the loop — the second bounce is the first
146
+ * one read back.
147
+ */
148
+ fn probeVolumeIrradianceAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
149
+ let counts = vec3<u32>(
150
+ max(1u, u32(volume.counts.x)),
151
+ max(1u, u32(volume.counts.y)),
152
+ max(1u, u32(volume.counts.z)),
153
+ );
154
+ let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);
155
+ let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);
156
+ let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);
157
+ let uv = probeOctInsetUv(n, volume.edge);
158
+
159
+ var sum = vec3<f32>(0.0);
160
+ for (var oz = 0u; oz < 2u; oz = oz + 1u) {
161
+ let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);
162
+ for (var oy = 0u; oy < 2u; oy = oy + 1u) {
163
+ let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);
164
+ for (var ox = 0u; ox < 2u; ox = ox + 1u) {
165
+ let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);
166
+ let weight = wx * wy * wz;
167
+ if (weight <= 0.0) { continue; }
168
+ let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);
169
+ sum = sum + probeLayerIrradiance(layer, uv) * weight;
170
+ }
171
+ }
172
+ }
173
+ return sum;
174
+ }
175
+
176
+ /* Where a layer's probe stands, which a parallax read needs and the lattice above decides. */
177
+ fn probeVolumeProbeAt(layer: u32, counts: vec3<u32>) -> vec3<f32> {
178
+ let x = layer % counts.x;
179
+ let y = (layer / counts.x) % counts.y;
180
+ let z = layer / (counts.x * counts.y);
181
+ let spacing = vec3<f32>(
182
+ select(0.0, 1.0 / volume.invSpacing.x, volume.invSpacing.x != 0.0),
183
+ select(0.0, 1.0 / volume.invSpacing.y, volume.invSpacing.y != 0.0),
184
+ select(0.0, 1.0 / volume.invSpacing.z, volume.invSpacing.z != 0.0),
185
+ );
186
+ return volume.origin + vec3<f32>(f32(x), f32(y), f32(z)) * spacing;
187
+ }
188
+
189
+ /*
190
+ * The radiance leaving a world point, as the grid saw it.
191
+ *
192
+ * **A ray needs radiance and the first version read irradiance**, which is what
193
+ * "demo/dev/bounce.html" measured as a bounce dimmer and greyer than the rasterised grid it was
194
+ * meant to replace. Irradiance is light *arriving* at a point, already cosine-convolved over a
195
+ * hemisphere; what a ray that struck a wall needs is the light *leaving* that wall towards it. The
196
+ * two differ by the surface's albedo — so a red wall and a white wall at the same irradiance
197
+ * returned the same colour, and the red never travelled.
198
+ *
199
+ * **Level 0 of the probe array is that radiance**, and it needs no albedo because it is a
200
+ * rasterised capture of the shaded room: the red wall is red in it. §6.4 of the design names
201
+ * "envProbeArray.ts" as this track's starting point and leaves how much of it to reuse to
202
+ * measurement, which is this.
203
+ *
204
+ * **Each corner is read along its own direction to the point**, which is what makes this a
205
+ * position lookup rather than a direction one. Eight probes sampled along one shared direction
206
+ * answer "what is over there from roughly here"; eight probes each sampled towards "p" answer
207
+ * "what is at "p"", and the difference is the parallax that a marched hit exists to provide. It is
208
+ * also why no direction is passed in: deriving it removes the reversed-direction mistake that
209
+ * "probe-bake-parity.mjs" records being unable to see.
210
+ */
211
+ fn probeVolumeRadianceAt(p: vec3<f32>) -> vec3<f32> {
212
+ let counts = vec3<u32>(
213
+ max(1u, u32(volume.counts.x)),
214
+ max(1u, u32(volume.counts.y)),
215
+ max(1u, u32(volume.counts.z)),
216
+ );
217
+ let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);
218
+ let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);
219
+ let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);
220
+
221
+ var sum = vec3<f32>(0.0);
222
+ for (var oz = 0u; oz < 2u; oz = oz + 1u) {
223
+ let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);
224
+ for (var oy = 0u; oy < 2u; oy = oy + 1u) {
225
+ let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);
226
+ for (var ox = 0u; ox < 2u; ox = ox + 1u) {
227
+ let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);
228
+ let weight = wx * wy * wz;
229
+ if (weight <= 0.0) { continue; }
230
+ let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);
231
+ /*
232
+ * From that probe towards the point, which is the parallax. A probe standing exactly on
233
+ * the point has no direction to offer and takes its own zeroth texel, which is as good an
234
+ * answer as exists for a point inside a probe.
235
+ */
236
+ let offset = p - probeVolumeProbeAt(layer, counts);
237
+ let reach = length(offset);
238
+ let towards = select(vec3<f32>(0.0, 0.0, 1.0), offset / reach, reach > 0.0);
239
+ let uv = probeOctInsetUv(towards, volume.radianceEdge);
240
+ sum = sum + probeLayerSample(layer, uv) * weight;
241
+ }
242
+ }
243
+ }
244
+ return sum;
245
+ }
246
+ `;
@@ -0,0 +1,140 @@
1
+ /** Reading the composed field on the device, the way `sampleGlobalField` reads it on the CPU. */
2
+
3
+ import { GLOBAL_FIELD_BLEND } from '../../gi/globalField.ts';
4
+
5
+ /**
6
+ * **One function, included by everything that reads the field, because two spellings drift.**
7
+ *
8
+ * `globalField.ts`'s `sampleGlobalField` is the reference: the finest cascade that holds a point
9
+ * answers, faded into the one outside it across the outer tenth of its extent, and a point past
10
+ * every cascade takes the outermost clamped at its edge. Every one of those three is a decision a
11
+ * second implementation would get subtly differently, so this is text rather than a copy — the
12
+ * march includes it, the parity entry point below includes it, and anything else that needs the
13
+ * field includes it too.
14
+ *
15
+ * **`GLOBAL_FIELD_BLEND` is interpolated in rather than restated**, which is the 2026-08-13 rule
16
+ * about one number in one place: a fade that is a tenth here and a twelfth there is a seam that
17
+ * moves with the camera and nothing in either file looks wrong.
18
+ */
19
+
20
+ /** Floats a cascade in the `cascades` buffer: six bounds, the step, and a pad to keep it aligned. */
21
+ export const CASCADE_FLOATS = 8;
22
+
23
+ /**
24
+ * The sampler, as WGSL text to include.
25
+ *
26
+ * Expects `cascades: array<f32>` and `fields: array<f32>` in scope, and takes the cascade count and
27
+ * the samples a side as arguments rather than reading them from a third buffer — a function that
28
+ * reads a global it was not given is one that cannot be moved.
29
+ */
30
+ export const SAMPLE_FIELD_WGSL = /* wgsl */ `
31
+ /* Trilinear read of one cascade, clamped at its own edge. The layout is x fastest, then y, then z. */
32
+ fn cascadeAt(level: u32, side: u32, p: vec3<f32>) -> f32 {
33
+ let base = level * ${CASCADE_FLOATS}u;
34
+ let low = vec3<f32>(cascades[base], cascades[base + 1u], cascades[base + 2u]);
35
+ let step = cascades[base + 6u];
36
+ let count = f32(side);
37
+
38
+ var corner = vec3<f32>(0.0, 0.0, 0.0);
39
+ var frac = vec3<f32>(0.0, 0.0, 0.0);
40
+ for (var axis = 0u; axis < 3u; axis = axis + 1u) {
41
+ let local = (p[axis] - low[axis]) / step;
42
+ let clamped = clamp(local, 0.0, count - 1.0);
43
+ /*
44
+ * **The same line guards different things on the two sides, and only one of them shows.**
45
+ * Stopping one plane short keeps the pair this interpolates between inside the cascade. Drop it
46
+ * and the corner at count carries weight zero, so the *value* is identical here — WGSL clamps
47
+ * an out-of-range storage read and zero times anything is zero — while the TypeScript reads
48
+ * \`undefined\` out of a \`Float32Array\` past its end and \`undefined * 0\` is NaN. Perturbing it
49
+ * fails \`globalField.test.ts\` and passes \`gi-parity.mjs\`. It stays because it is what keeps
50
+ * the read inside this cascade rather than in the next one's samples, which share the buffer.
51
+ */
52
+ let index = min(floor(clamped), count - 2.0);
53
+ corner[axis] = index;
54
+ frac[axis] = clamped - index;
55
+ }
56
+
57
+ let offset = level * side * side * side;
58
+ let lx = u32(corner.x);
59
+ let ly = u32(corner.y);
60
+ let lz = u32(corner.z);
61
+ var total = 0.0;
62
+ for (var cz = 0u; cz < 2u; cz = cz + 1u) {
63
+ let wz = select(1.0 - frac.z, frac.z, cz == 1u);
64
+ for (var cy = 0u; cy < 2u; cy = cy + 1u) {
65
+ let wy = select(1.0 - frac.y, frac.y, cy == 1u);
66
+ for (var cx = 0u; cx < 2u; cx = cx + 1u) {
67
+ let wx = select(1.0 - frac.x, frac.x, cx == 1u);
68
+ let at = lx + cx + side * (ly + cy + side * (lz + cz));
69
+ total = total + fields[offset + at] * wx * wy * wz;
70
+ }
71
+ }
72
+ }
73
+ return total;
74
+ }
75
+
76
+ /* How far inside a cascade a point is, measured to the nearest face. Negative means outside. */
77
+ fn insideBy(level: u32, p: vec3<f32>) -> f32 {
78
+ let base = level * ${CASCADE_FLOATS}u;
79
+ let low = vec3<f32>(cascades[base], cascades[base + 1u], cascades[base + 2u]);
80
+ let high = vec3<f32>(cascades[base + 3u], cascades[base + 4u], cascades[base + 5u]);
81
+ let gap = min(p - low, high - p);
82
+ return min(gap.x, min(gap.y, gap.z));
83
+ }
84
+
85
+ /*
86
+ * The field at a world point, from the finest cascade that holds it.
87
+ *
88
+ * Faded into the cascade outside across the outer tenth of the extent, because every boundary here
89
+ * moves with the camera — the cascades are centred on it — and a hard switch at a boundary that
90
+ * moves is a seam sweeping through the picture whenever the camera turns.
91
+ */
92
+ fn sampleField(levels: u32, side: u32, p: vec3<f32>) -> f32 {
93
+ let last = levels - 1u;
94
+ for (var level = 0u; level <= last; level = level + 1u) {
95
+ let inside = insideBy(level, p);
96
+ if (inside <= 0.0) {
97
+ continue;
98
+ }
99
+ let value = cascadeAt(level, side, p);
100
+ if (level == last) {
101
+ return value;
102
+ }
103
+ let base = level * ${CASCADE_FLOATS}u;
104
+ let band = ${GLOBAL_FIELD_BLEND.toFixed(6)} * (cascades[base + 3u] - cascades[base]);
105
+ if (inside >= band) {
106
+ return value;
107
+ }
108
+ let outer = cascadeAt(level + 1u, side, p);
109
+ return mix(outer, value, inside / band);
110
+ }
111
+ return cascadeAt(last, side, p);
112
+ }
113
+ `;
114
+
115
+ /** Floats in the sampler parity entry point's params: cascade count, samples a side, point count. */
116
+ export const SAMPLE_PARAM_FLOATS = 4;
117
+
118
+ /**
119
+ * A compute entry point that does nothing but sample, so `gi-parity.mjs` can compare the function
120
+ * above against `sampleGlobalField` directly rather than through whatever is using it.
121
+ */
122
+ export const SAMPLE_FIELD_PARITY_WGSL = /* wgsl */ `
123
+ @group(0) @binding(0) var<storage, read> params: array<f32>;
124
+ @group(0) @binding(1) var<storage, read> cascades: array<f32>;
125
+ @group(0) @binding(2) var<storage, read> fields: array<f32>;
126
+ @group(0) @binding(3) var<storage, read> points: array<f32>;
127
+ @group(0) @binding(4) var<storage, read_write> out: array<f32>;
128
+
129
+ ${SAMPLE_FIELD_WGSL}
130
+
131
+ @compute @workgroup_size(64)
132
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
133
+ let count = u32(params[2]);
134
+ if (id.x >= count) {
135
+ return;
136
+ }
137
+ let p = vec3<f32>(points[id.x * 3u], points[id.x * 3u + 1u], points[id.x * 3u + 2u]);
138
+ out[id.x] = sampleField(u32(params[0]), u32(params[1]), p);
139
+ }
140
+ `;