@driftengine/core 3.63.0 → 4.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 (428) hide show
  1. package/CHANGELOG.json +122 -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,22 @@
1
+ import { readsOf, writesOf } from './deps.js';
2
+ export function validateGraph(deps, count, imported) {
3
+ const written = new Set(imported);
4
+ for (let node = 0; node < count; node += 1) {
5
+ /*
6
+ * Reads are checked before this node's writes join the set, which is what makes a read of
7
+ * something written only later a refusal and a read-modify-write legal — the latter is
8
+ * reading contents something earlier put there.
9
+ */
10
+ const reads = readsOf(deps, node);
11
+ for (let i = 0; i < reads.length; i += 1) {
12
+ const id = reads[i] ?? 0;
13
+ if (!written.has(id)) {
14
+ return `node ${node} reads resource ${id}, which nothing wrote and nothing imported`;
15
+ }
16
+ }
17
+ const writes = writesOf(deps, node);
18
+ for (let i = 0; i < writes.length; i += 1)
19
+ written.add(writes[i] ?? 0);
20
+ }
21
+ return null;
22
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Transient resources a frame declares, addressed by identifier rather than by bit.
3
+ *
4
+ * **`resources.ts` is not replaced and is not wrong.** It is a fixed table of fourteen
5
+ * attachments held in a bit mask, and it says in its own header that the transient allocator a
6
+ * frame graph is famous for would be machinery with no case to serve — which was true while
7
+ * this frame's composition was fixed. A GPU-driven frame's composition is not: hierarchical
8
+ * depth chains, visibility buffers, per-material bin lists and indirect argument buffers are
9
+ * allocated against what the frame contains. Thirty-one bits cannot hold hundreds of them and
10
+ * a thirty-second bit is not the fix.
11
+ *
12
+ * So there are two representations and each is used where it is right: the mask for a fixed
13
+ * composition, identifiers for a dynamic one. Nothing converts between them, because nothing
14
+ * needs to — a frame is scheduled by one or the other.
15
+ *
16
+ * Structure of arrays, growable by doubling, and nothing here holds an object per resource.
17
+ */
18
+ /** A transient texture, sized in texels. */
19
+ export interface VirtualTextureDesc {
20
+ kind: 'texture';
21
+ width: number;
22
+ height: number;
23
+ bytesPerTexel: number;
24
+ }
25
+ /** A transient buffer, sized in bytes. */
26
+ export interface VirtualBufferDesc {
27
+ kind: 'buffer';
28
+ bytes: number;
29
+ }
30
+ export type VirtualDesc = VirtualTextureDesc | VirtualBufferDesc;
31
+ export interface VirtualTable {
32
+ /** 0 for a texture, 1 for a buffer, one entry per identifier. */
33
+ kinds: Uint8Array;
34
+ /** Byte footprint per identifier, which is what the aliasing allocator packs. */
35
+ bytes: Uint32Array;
36
+ /** How many identifiers have been declared this frame. */
37
+ count: number;
38
+ }
39
+ export declare function createVirtualTable(capacity: number): VirtualTable;
40
+ export declare function resetVirtualTable(table: VirtualTable): void;
41
+ export declare function virtualCount(table: VirtualTable): number;
42
+ export declare function declareVirtual(table: VirtualTable, desc: VirtualDesc): number;
43
+ export declare function virtualKind(table: VirtualTable, id: number): number;
44
+ export declare function virtualBytes(table: VirtualTable, id: number): number;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Transient resources a frame declares, addressed by identifier rather than by bit.
3
+ *
4
+ * **`resources.ts` is not replaced and is not wrong.** It is a fixed table of fourteen
5
+ * attachments held in a bit mask, and it says in its own header that the transient allocator a
6
+ * frame graph is famous for would be machinery with no case to serve — which was true while
7
+ * this frame's composition was fixed. A GPU-driven frame's composition is not: hierarchical
8
+ * depth chains, visibility buffers, per-material bin lists and indirect argument buffers are
9
+ * allocated against what the frame contains. Thirty-one bits cannot hold hundreds of them and
10
+ * a thirty-second bit is not the fix.
11
+ *
12
+ * So there are two representations and each is used where it is right: the mask for a fixed
13
+ * composition, identifiers for a dynamic one. Nothing converts between them, because nothing
14
+ * needs to — a frame is scheduled by one or the other.
15
+ *
16
+ * Structure of arrays, growable by doubling, and nothing here holds an object per resource.
17
+ */
18
+ export function createVirtualTable(capacity) {
19
+ return { kinds: new Uint8Array(capacity), bytes: new Uint32Array(capacity), count: 0 };
20
+ }
21
+ export function resetVirtualTable(table) {
22
+ table.count = 0;
23
+ }
24
+ export function virtualCount(table) {
25
+ return table.count;
26
+ }
27
+ /*
28
+ * Doubling rather than growing by one, because a frame that declares one more resource than
29
+ * last frame will declare one more again, and a table that reallocates per declaration turns a
30
+ * linear frame into a quadratic one.
31
+ */
32
+ function grow(table) {
33
+ const size = table.kinds.length === 0 ? 8 : table.kinds.length * 2;
34
+ const kinds = new Uint8Array(size);
35
+ kinds.set(table.kinds);
36
+ const bytes = new Uint32Array(size);
37
+ bytes.set(table.bytes);
38
+ table.kinds = kinds;
39
+ table.bytes = bytes;
40
+ }
41
+ export function declareVirtual(table, desc) {
42
+ if (table.count === table.kinds.length)
43
+ grow(table);
44
+ const id = table.count;
45
+ if (desc.kind === 'texture') {
46
+ table.kinds[id] = 0;
47
+ table.bytes[id] = desc.width * desc.height * desc.bytesPerTexel;
48
+ }
49
+ else {
50
+ table.kinds[id] = 1;
51
+ table.bytes[id] = desc.bytes;
52
+ }
53
+ table.count = id + 1;
54
+ return id;
55
+ }
56
+ export function virtualKind(table, id) {
57
+ return table.kinds[id] ?? 0;
58
+ }
59
+ export function virtualBytes(table, id) {
60
+ return table.bytes[id] ?? 0;
61
+ }
@@ -0,0 +1,82 @@
1
+ /** The three levels in one place, and the rule that there is never a fourth. */
2
+ import type { ReadonlyVec3 } from 'gl-matrix';
3
+ import type { ProbeGrid } from '../probeGrid.ts';
4
+ import type { ScreenSpaceMarch } from '../screenSpaceReflection.ts';
5
+ import type { GlobalField } from './globalField.ts';
6
+ import type { ProbeVisibility } from './probeVolume.ts';
7
+ import type { FieldMarch } from './traceField.ts';
8
+ /**
9
+ * **Three levels, and the architecture is the fallback rather than any one of them.**
10
+ *
11
+ * `ROADMAP.md` refused screen-space global illumination, and this does not reverse that refusal —
12
+ * it satisfies the requirement the refusal was about. Screen space is exact wherever the screen has
13
+ * the answer and has *no* answer where it does not, which as a terminal technique means inventing
14
+ * one from whatever happened to be in frame. Here it is an accelerator with something behind it:
15
+ * a world-space distance field whose error is bounded by its own resolution, and behind that a
16
+ * probe volume which is never wrong and only ever coarse.
17
+ *
18
+ * **The last level cannot fail, and that is the load-bearing sentence.** A ray with no answer
19
+ * leaves a shader nothing to write but what the buffer held, which is the previous frame and reads
20
+ * as a flicker. A probe volume is an interpolation between measurements — no ray to escape, no
21
+ * frame to leave — and `visibleProbes` falls back to the nearest probe even when every visibility
22
+ * weight has crushed to zero. So there are exactly three cases.
23
+ *
24
+ * **And the levels blend rather than switch.** Every boundary between them moves with the camera:
25
+ * the frame's edge does by definition, and the cascades are centred on the camera so their faces do
26
+ * too. A hard switch at a boundary that moves is a seam that sweeps across the picture whenever the
27
+ * camera turns, which is worse than either level's own error — an error that is wrong everywhere
28
+ * reads as a coarse solution, and one that changes abruptly along a surface reads as a crack.
29
+ */
30
+ export declare const GI_SOURCE_SCREEN = 0;
31
+ export declare const GI_SOURCE_FIELD = 1;
32
+ export declare const GI_SOURCE_PROBES = 2;
33
+ export type GiSource = 0 | 1 | 2;
34
+ /** One indirect ray, as the shading path hands it over. */
35
+ export interface IndirectRay {
36
+ /** Where it starts, on the surface being shaded. */
37
+ readonly point: ReadonlyVec3;
38
+ /** That surface's normal, which both traces lift off along. */
39
+ readonly normal: ReadonlyVec3;
40
+ readonly direction: ReadonlyVec3;
41
+ /** Cone half-angle for the world trace. Zero is a mirror; wider is a rougher surface. */
42
+ readonly coneAngle: number;
43
+ }
44
+ /** Everything the three levels need, gathered so a per-ray call takes one object. */
45
+ export interface GiResources {
46
+ readonly eye: ReadonlyVec3;
47
+ readonly project: (x: number, y: number, z: number, uv: Float32Array) => boolean;
48
+ /** How far the eye is from whatever the frame drew, and `Infinity` where it drew nothing. */
49
+ readonly sceneDistance: (u: number, v: number) => number;
50
+ /** The radiance the frame holds at a screen position. */
51
+ readonly screenRadiance: (u: number, v: number, out: Float32Array) => void;
52
+ readonly field: GlobalField;
53
+ /** The radiance at a world point the field trace arrived at. */
54
+ readonly fieldRadiance: (x: number, y: number, z: number, out: Float32Array) => void;
55
+ readonly grid: ProbeGrid;
56
+ /** Null is the off switch, and it lands on the grid's own blend. See `probeVolume.ts`. */
57
+ readonly visibility: ProbeVisibility | null;
58
+ /** `probeChannels` floats a probe, layer-major. */
59
+ readonly probeValues: Float32Array;
60
+ readonly probeChannels: number;
61
+ readonly screenMarch?: ScreenSpaceMarch;
62
+ readonly fieldMarch?: FieldMarch;
63
+ }
64
+ export interface IndirectResult {
65
+ /** Always true. See the module header: there is no fourth case. */
66
+ hit: boolean;
67
+ /** Which level contributed most. The radiance is the blend, not this level alone. */
68
+ source: GiSource;
69
+ /** Three channels. */
70
+ readonly radiance: Float32Array;
71
+ /** How much of the answer came from each level, summing to one. */
72
+ readonly blend: Float32Array;
73
+ }
74
+ export declare function newIndirectResult(): IndirectResult;
75
+ /**
76
+ * Trace one indirect ray down the chain, and always answer.
77
+ *
78
+ * The screen answers as much as its distance from the edge of the frame allows; what is left goes
79
+ * to the world field, as much as *its* distance from the edge of the outermost cascade allows; and
80
+ * whatever is left after that goes to the probes, which have nothing behind them and take it all.
81
+ */
82
+ export declare function traceIndirect(ray: IndirectRay, resources: GiResources, out: IndirectResult): IndirectResult;
@@ -0,0 +1,135 @@
1
+ /** The three levels in one place, and the rule that there is never a fourth. */
2
+ import { createProbeBlend } from '../probeGrid.js';
3
+ import { REFLECTION_EDGE_FADE, edgeFade, newScreenSpaceHit } from '../screenSpaceReflection.js';
4
+ import { GLOBAL_FIELD_BLEND } from './globalField.js';
5
+ import { sampleProbeVolume, visibleProbes } from './probeVolume.js';
6
+ import { FIELD_MARCH, newFieldHit, traceField } from './traceField.js';
7
+ import { GI_SCREEN_MARCH, traceScreen } from './traceScreen.js';
8
+ /**
9
+ * **Three levels, and the architecture is the fallback rather than any one of them.**
10
+ *
11
+ * `ROADMAP.md` refused screen-space global illumination, and this does not reverse that refusal —
12
+ * it satisfies the requirement the refusal was about. Screen space is exact wherever the screen has
13
+ * the answer and has *no* answer where it does not, which as a terminal technique means inventing
14
+ * one from whatever happened to be in frame. Here it is an accelerator with something behind it:
15
+ * a world-space distance field whose error is bounded by its own resolution, and behind that a
16
+ * probe volume which is never wrong and only ever coarse.
17
+ *
18
+ * **The last level cannot fail, and that is the load-bearing sentence.** A ray with no answer
19
+ * leaves a shader nothing to write but what the buffer held, which is the previous frame and reads
20
+ * as a flicker. A probe volume is an interpolation between measurements — no ray to escape, no
21
+ * frame to leave — and `visibleProbes` falls back to the nearest probe even when every visibility
22
+ * weight has crushed to zero. So there are exactly three cases.
23
+ *
24
+ * **And the levels blend rather than switch.** Every boundary between them moves with the camera:
25
+ * the frame's edge does by definition, and the cascades are centred on the camera so their faces do
26
+ * too. A hard switch at a boundary that moves is a seam that sweeps across the picture whenever the
27
+ * camera turns, which is worse than either level's own error — an error that is wrong everywhere
28
+ * reads as a coarse solution, and one that changes abruptly along a surface reads as a crack.
29
+ */
30
+ export const GI_SOURCE_SCREEN = 0;
31
+ export const GI_SOURCE_FIELD = 1;
32
+ export const GI_SOURCE_PROBES = 2;
33
+ export function newIndirectResult() {
34
+ return {
35
+ hit: false,
36
+ source: GI_SOURCE_PROBES,
37
+ radiance: new Float32Array(3),
38
+ blend: new Float32Array(3),
39
+ };
40
+ }
41
+ /** Scratch, because this runs once per ray per pixel and the rule about that is absolute. */
42
+ const SCREEN_HIT = newScreenSpaceHit();
43
+ const FIELD_HIT = newFieldHit();
44
+ const PROBE_BLEND = createProbeBlend();
45
+ const LEVEL = new Float32Array(3);
46
+ const PROBE_OUT = new Float32Array(3);
47
+ /**
48
+ * Trace one indirect ray down the chain, and always answer.
49
+ *
50
+ * The screen answers as much as its distance from the edge of the frame allows; what is left goes
51
+ * to the world field, as much as *its* distance from the edge of the outermost cascade allows; and
52
+ * whatever is left after that goes to the probes, which have nothing behind them and take it all.
53
+ */
54
+ export function traceIndirect(ray, resources, out) {
55
+ out.radiance.fill(0);
56
+ out.blend.fill(0);
57
+ out.hit = true;
58
+ /* Level 0. Weighted by how far the hit landed from the edge of the frame, which is the one
59
+ boundary a viewer is guaranteed to see move. */
60
+ let screen = 0;
61
+ if (traceScreen(ray.point, ray.normal, ray.direction, resources.eye, resources.screenMarch ?? GI_SCREEN_MARCH, resources.project, resources.sceneDistance, SCREEN_HIT)) {
62
+ /*
63
+ * **The *path*'s margin, not the hit's.** Fading only the hit leaves the seam exactly where it
64
+ * was: a ray that grazes the border and lands is faded, and the ray one pixel over leaves the
65
+ * frame and answers nothing at all, so the weight still steps from one to zero across a line
66
+ * that sweeps as the camera turns. See `ScreenSpaceHit.margin`.
67
+ */
68
+ screen = edgeFade(SCREEN_HIT.margin, REFLECTION_EDGE_FADE);
69
+ if (screen > 0) {
70
+ resources.screenRadiance(SCREEN_HIT.u, SCREEN_HIT.v, LEVEL);
71
+ add(out.radiance, LEVEL, screen);
72
+ }
73
+ }
74
+ /* Level 1, for whatever the screen did not answer. Weighted the same way against the outermost
75
+ cascade's face, because that face is centred on the camera and therefore moves with it too. */
76
+ let fieldShare = 0;
77
+ const remaining = 1 - screen;
78
+ if (remaining > 0) {
79
+ const march = resources.fieldMarch ?? FIELD_MARCH;
80
+ const coned = march.coneAngle === ray.coneAngle ? march : { ...march, coneAngle: ray.coneAngle };
81
+ if (traceField(resources.field, ray.point, ray.normal, ray.direction, coned, FIELD_HIT)) {
82
+ fieldShare =
83
+ remaining * fieldConfidence(resources.field, FIELD_HIT.x, FIELD_HIT.y, FIELD_HIT.z);
84
+ if (fieldShare > 0) {
85
+ resources.fieldRadiance(FIELD_HIT.x, FIELD_HIT.y, FIELD_HIT.z, LEVEL);
86
+ add(out.radiance, LEVEL, fieldShare);
87
+ }
88
+ }
89
+ }
90
+ /*
91
+ * Level 2, for everything still unanswered. **It takes whatever is left without asking**, which
92
+ * is what "never wrong and only ever coarse" buys: there is no case where this declines.
93
+ */
94
+ const probes = 1 - screen - fieldShare;
95
+ if (probes > 0) {
96
+ visibleProbes(resources.grid, resources.visibility, ray.point[0] ?? 0, ray.point[1] ?? 0, ray.point[2] ?? 0, PROBE_BLEND);
97
+ sampleProbeVolume(PROBE_BLEND, resources.probeValues, resources.probeChannels, PROBE_OUT);
98
+ add(out.radiance, PROBE_OUT, probes);
99
+ }
100
+ out.blend[0] = screen;
101
+ out.blend[1] = fieldShare;
102
+ out.blend[2] = probes;
103
+ out.source =
104
+ screen >= fieldShare && screen >= probes
105
+ ? GI_SOURCE_SCREEN
106
+ : fieldShare >= probes
107
+ ? GI_SOURCE_FIELD
108
+ : GI_SOURCE_PROBES;
109
+ return out;
110
+ }
111
+ /**
112
+ * How much a world-field hit is trusted, by how far inside the outermost cascade it landed.
113
+ *
114
+ * **The same band `sampleGlobalField` fades its own cascades over**, so the two do not disagree
115
+ * about where the field stops being the answer. A hit right on the outer face is a hit the field
116
+ * barely had the information for, and the probes are what carry it the rest of the way.
117
+ */
118
+ function fieldConfidence(field, x, y, z) {
119
+ const outer = field.cascades[field.cascades.length - 1];
120
+ const band = GLOBAL_FIELD_BLEND * (outer.bounds[3] - outer.bounds[0]);
121
+ if (band <= 0)
122
+ return 1;
123
+ const p = [x, y, z];
124
+ let inside = Infinity;
125
+ for (let axis = 0; axis < 3; axis++) {
126
+ inside = Math.min(inside, p[axis] - outer.bounds[axis], outer.bounds[axis + 3] - p[axis]);
127
+ }
128
+ return Math.min(1, Math.max(0, inside / band));
129
+ }
130
+ /** `target += source * weight`, three channels. */
131
+ function add(target, source, weight) {
132
+ for (let channel = 0; channel < 3; channel++) {
133
+ target[channel] = target[channel] + source[channel] * weight;
134
+ }
135
+ }
@@ -0,0 +1,114 @@
1
+ /** Filtering indirect light without smearing it across an edge or losing any of it. */
2
+ import type { GiSource } from './chain.ts';
3
+ /**
4
+ * **Indirect light arrives noisy, and every way of filtering it can quietly darken the scene.**
5
+ *
6
+ * A handful of rays a pixel is what makes this affordable, and a handful of rays is an estimate
7
+ * with variance. The filter is not a finishing touch — without it the chain is unusable — and the
8
+ * three ways it goes wrong all read as the *lighting* wanting a tuning pass rather than as the
9
+ * filter being wrong: a spatial kernel whose weights do not sum to one darkens every edge, a
10
+ * temporal blend that treats a disocclusion as staleness smears whatever moved, and a blend weight
11
+ * that never settles either oscillates or never reaches the mean it is averaging.
12
+ *
13
+ * **The plan asked this to reuse Wave 3A's reprojection, disocclusion and clamping rather than
14
+ * writing a second temporal filter, and one of those three exists.** `clipToNeighbourhood` is
15
+ * here, tested, and mirrored in `TEMPORAL_RESOLVE_FRAG` — so it is used. The other two do not
16
+ * exist in the tree and Wave 3A creates them, which is why **this module takes them as inputs
17
+ * rather than computing them**: `temporalDenoise` is handed the history a caller reprojected and a
18
+ * flag saying whether it may be trusted. That is the right shape regardless of which wave lands
19
+ * first — a denoiser that owned its own reprojection would be the second answer the plan was
20
+ * warning about — and it means the whole of this is testable and shippable today, with Wave 3A
21
+ * plugging into the two parameters rather than into a rewrite.
22
+ */
23
+ /** One frame's worth of what the filter needs, as flat arrays a pass would upload. */
24
+ export interface DenoiseFrame {
25
+ /** Three floats a pixel. */
26
+ readonly radiance: Float32Array;
27
+ /** One a pixel, in metres from the eye. A pixel that drew nothing may be `Infinity`. */
28
+ readonly depth: Float32Array;
29
+ /** Three a pixel, unit length. */
30
+ readonly normal: Float32Array;
31
+ /** Which level of the chain answered each pixel. See `filterStepFor`. */
32
+ readonly source: Uint8Array;
33
+ readonly width: number;
34
+ readonly height: number;
35
+ }
36
+ export interface SpatialOptions {
37
+ /**
38
+ * Multiplier on the kernel's spacing, in pixels. See `filterStepFor` for what chooses it.
39
+ *
40
+ * **A step rather than a radius**, which is the à-trous arrangement: the same five-tap kernel is
41
+ * run with its taps further apart, so a wide filter costs what a narrow one costs. A radius
42
+ * would cost its own square.
43
+ */
44
+ readonly step: number;
45
+ /**
46
+ * How far apart in depth two pixels may be and still share light, as a fraction of their depth.
47
+ *
48
+ * **Relative rather than absolute**, because a centimetre is a discontinuity across a desk and
49
+ * nothing at all across a valley. A tenth is wide enough to keep a surface tilted away from the
50
+ * camera together and narrow enough to find the edge of the thing standing on it.
51
+ */
52
+ readonly depthTolerance?: number;
53
+ /** The exponent on `max(dot(n, n'), 0)`. Higher keeps the filter inside one face. */
54
+ readonly normalSharpness?: number;
55
+ }
56
+ /**
57
+ * How far apart the taps go, for light that came from each level of the chain.
58
+ *
59
+ * **Task 6's `source` discriminant is what this is for**, and it is the reason that field is on
60
+ * `IndirectResult` at all. A sample the screen answered is as sharp as the frame it came from and
61
+ * wants the narrowest filter there is. One the world field answered stands for a cone's worth of
62
+ * surface. One the probes answered is already an interpolation over metres, and filtering it
63
+ * narrowly buys nothing — its noise is not at pixel scale.
64
+ *
65
+ * Filtering all three at one width either smears the sharp one or leaves the coarse one noisy, and
66
+ * which of those a viewer notices depends on where they are standing.
67
+ */
68
+ export declare function filterStepFor(source: GiSource): number;
69
+ /**
70
+ * One separable, edge-stopping pass over a frame.
71
+ *
72
+ * **The weights are renormalised by their own sum, and that is the line this module exists to get
73
+ * right.** A tap rejected for being on the other side of an edge has to be *removed from the
74
+ * average*, not set to zero and left in the denominator — the second darkens every silhouette by
75
+ * however much of its kernel fell off the surface, which looks like contact shadowing and is the
76
+ * filter eating light. `energy.test.ts` makes the same argument about the probe blend one level up.
77
+ */
78
+ export declare function spatialDenoise(frame: DenoiseFrame, options: SpatialOptions, out: Float32Array): void;
79
+ /**
80
+ * The least weight this frame may be given, however long a pixel has been accumulating.
81
+ *
82
+ * **An honest running mean converges and then stops listening.** One over the count is the
83
+ * unbiased average of everything seen, which is exactly what is wanted while a pixel is settling
84
+ * and exactly wrong once the world changes: at ten thousand frames a new value moves the answer by
85
+ * a ten-thousandth, so a light switched on takes minutes to arrive. A twentieth is the floor, which
86
+ * is about a third of a second at sixty frames and is the same order as
87
+ * `TEMPORAL_HISTORY_BLEND`'s own one-tenth.
88
+ */
89
+ export declare const DENOISE_MIN_ALPHA = 0.05;
90
+ /** What a pixel carries between frames. The caller owns one per pixel. */
91
+ export interface TemporalPixel {
92
+ /** The accumulated answer. */
93
+ readonly value: Float32Array;
94
+ /** How many frames have contributed since the last disocclusion. */
95
+ samples: number;
96
+ }
97
+ export declare function newTemporalPixel(): TemporalPixel;
98
+ /**
99
+ * Blend this frame into the history a caller reprojected, and answer how much history there is.
100
+ *
101
+ * `history` is where this pixel was last frame, already reprojected — **this module does not do
102
+ * that and is not the place for it.** `disoccluded` says the reprojection landed on something that
103
+ * is no longer there; `neighbourhood`, when supplied, is the box of colours actually around this
104
+ * pixel this frame, and the history is clipped into it by `temporalAa.ts`'s own function.
105
+ *
106
+ * **A disocclusion resets rather than blends.** It is not a stale history, it is a wrong one: a
107
+ * character stepping away from a wall uncovers pixels whose history is the character, and blending
108
+ * nine parts of that is a smear of them that follows them about. Resetting leaves one frame of
109
+ * noise, which the spatial pass has already reduced and which is gone by the next frame.
110
+ */
111
+ export declare function temporalDenoise(current: readonly [number, number, number] | Float32Array, history: readonly [number, number, number] | Float32Array, neighbourhood: {
112
+ readonly min: readonly [number, number, number];
113
+ readonly max: readonly [number, number, number];
114
+ } | null, disoccluded: boolean, pixel: TemporalPixel, out: Float32Array): TemporalPixel;
@@ -0,0 +1,169 @@
1
+ /** Filtering indirect light without smearing it across an edge or losing any of it. */
2
+ import { clipToNeighbourhood } from '../temporalAa.js';
3
+ const DEFAULT_DEPTH_TOLERANCE = 0.1;
4
+ const DEFAULT_NORMAL_SHARPNESS = 32;
5
+ /**
6
+ * The five-tap row, which is the kernel a separable à-trous pass runs twice.
7
+ *
8
+ * `[1, 4, 6, 4, 1] / 16`, a binomial — the discrete Gaussian, and the one every implementation of
9
+ * this uses. It is written here rather than computed so a reader can see that it is symmetric and
10
+ * that it sums to one *before* any of the edge-stopping weights touch it.
11
+ */
12
+ const KERNEL = [1 / 16, 4 / 16, 6 / 16, 4 / 16, 1 / 16];
13
+ /**
14
+ * How far apart the taps go, for light that came from each level of the chain.
15
+ *
16
+ * **Task 6's `source` discriminant is what this is for**, and it is the reason that field is on
17
+ * `IndirectResult` at all. A sample the screen answered is as sharp as the frame it came from and
18
+ * wants the narrowest filter there is. One the world field answered stands for a cone's worth of
19
+ * surface. One the probes answered is already an interpolation over metres, and filtering it
20
+ * narrowly buys nothing — its noise is not at pixel scale.
21
+ *
22
+ * Filtering all three at one width either smears the sharp one or leaves the coarse one noisy, and
23
+ * which of those a viewer notices depends on where they are standing.
24
+ */
25
+ export function filterStepFor(source) {
26
+ return source === 0 ? 1 : source === 1 ? 2 : 4;
27
+ }
28
+ /** Scratch, because a filter pass is per pixel and the rule about allocating in one is absolute. */
29
+ const TOTAL = new Float32Array(3);
30
+ const CLIPPED = [0, 0, 0];
31
+ /**
32
+ * One separable, edge-stopping pass over a frame.
33
+ *
34
+ * **The weights are renormalised by their own sum, and that is the line this module exists to get
35
+ * right.** A tap rejected for being on the other side of an edge has to be *removed from the
36
+ * average*, not set to zero and left in the denominator — the second darkens every silhouette by
37
+ * however much of its kernel fell off the surface, which looks like contact shadowing and is the
38
+ * filter eating light. `energy.test.ts` makes the same argument about the probe blend one level up.
39
+ */
40
+ export function spatialDenoise(frame, options, out) {
41
+ const depthTolerance = options.depthTolerance ?? DEFAULT_DEPTH_TOLERANCE;
42
+ const normalSharpness = options.normalSharpness ?? DEFAULT_NORMAL_SHARPNESS;
43
+ /* Horizontal into `out`, then vertical in place. Separable, so a five-tap kernel is ten taps
44
+ rather than twenty-five, and the two halves cannot disagree because they are one function. */
45
+ pass(frame, frame.radiance, out, options.step, depthTolerance, normalSharpness, true);
46
+ pass(frame, out, out, options.step, depthTolerance, normalSharpness, false);
47
+ }
48
+ function pass(frame, input, out, step, depthTolerance, normalSharpness, horizontal) {
49
+ const { width, height, depth, normal, source } = frame;
50
+ /*
51
+ * A second pass reading its own output would filter the taps it has already written, which is a
52
+ * wider and lopsided kernel rather than the one declared. Copied when the two alias.
53
+ */
54
+ const read = input === out ? new Float32Array(out) : input;
55
+ for (let y = 0; y < height; y++) {
56
+ for (let x = 0; x < width; x++) {
57
+ const at = y * width + x;
58
+ const centreDepth = depth[at];
59
+ const nx = normal[at * 3];
60
+ const ny = normal[at * 3 + 1];
61
+ const nz = normal[at * 3 + 2];
62
+ const spacing = Math.max(1, Math.round(step * filterStepFor(source[at])));
63
+ TOTAL.fill(0);
64
+ let weightSum = 0;
65
+ for (let tap = 0; tap < KERNEL.length; tap++) {
66
+ const offset = (tap - 2) * spacing;
67
+ const sx = horizontal ? x + offset : x;
68
+ const sy = horizontal ? y : y + offset;
69
+ if (sx < 0 || sx >= width || sy < 0 || sy >= height)
70
+ continue;
71
+ const from = sy * width + sx;
72
+ /* A pixel that drew nothing has no surface to share light with. */
73
+ const tapDepth = depth[from];
74
+ if (!Number.isFinite(tapDepth) || !Number.isFinite(centreDepth)) {
75
+ if (from !== at)
76
+ continue;
77
+ }
78
+ const reference = Math.max(Math.abs(centreDepth), 1e-3);
79
+ const apart = Math.abs(tapDepth - centreDepth) / reference;
80
+ if (apart > depthTolerance)
81
+ continue;
82
+ const alignment = nx * normal[from * 3] +
83
+ ny * normal[from * 3 + 1] +
84
+ nz * normal[from * 3 + 2];
85
+ const facing = Math.max(alignment, 0) ** normalSharpness;
86
+ if (facing <= 1e-6)
87
+ continue;
88
+ const weight = KERNEL[tap] * facing;
89
+ TOTAL[0] = TOTAL[0] + read[from * 3] * weight;
90
+ TOTAL[1] = TOTAL[1] + read[from * 3 + 1] * weight;
91
+ TOTAL[2] = TOTAL[2] + read[from * 3 + 2] * weight;
92
+ weightSum += weight;
93
+ }
94
+ if (weightSum > 0) {
95
+ out[at * 3] = TOTAL[0] / weightSum;
96
+ out[at * 3 + 1] = TOTAL[1] / weightSum;
97
+ out[at * 3 + 2] = TOTAL[2] / weightSum;
98
+ }
99
+ else {
100
+ /* Every neighbour was rejected, including itself: keep what was there rather than black. */
101
+ out[at * 3] = read[at * 3];
102
+ out[at * 3 + 1] = read[at * 3 + 1];
103
+ out[at * 3 + 2] = read[at * 3 + 2];
104
+ }
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * The least weight this frame may be given, however long a pixel has been accumulating.
110
+ *
111
+ * **An honest running mean converges and then stops listening.** One over the count is the
112
+ * unbiased average of everything seen, which is exactly what is wanted while a pixel is settling
113
+ * and exactly wrong once the world changes: at ten thousand frames a new value moves the answer by
114
+ * a ten-thousandth, so a light switched on takes minutes to arrive. A twentieth is the floor, which
115
+ * is about a third of a second at sixty frames and is the same order as
116
+ * `TEMPORAL_HISTORY_BLEND`'s own one-tenth.
117
+ */
118
+ export const DENOISE_MIN_ALPHA = 0.05;
119
+ export function newTemporalPixel() {
120
+ return { value: new Float32Array(3), samples: 0 };
121
+ }
122
+ /**
123
+ * Blend this frame into the history a caller reprojected, and answer how much history there is.
124
+ *
125
+ * `history` is where this pixel was last frame, already reprojected — **this module does not do
126
+ * that and is not the place for it.** `disoccluded` says the reprojection landed on something that
127
+ * is no longer there; `neighbourhood`, when supplied, is the box of colours actually around this
128
+ * pixel this frame, and the history is clipped into it by `temporalAa.ts`'s own function.
129
+ *
130
+ * **A disocclusion resets rather than blends.** It is not a stale history, it is a wrong one: a
131
+ * character stepping away from a wall uncovers pixels whose history is the character, and blending
132
+ * nine parts of that is a smear of them that follows them about. Resetting leaves one frame of
133
+ * noise, which the spatial pass has already reduced and which is gone by the next frame.
134
+ */
135
+ export function temporalDenoise(current, history, neighbourhood, disoccluded, pixel, out) {
136
+ if (disoccluded) {
137
+ pixel.samples = 1;
138
+ for (let c = 0; c < 3; c++) {
139
+ pixel.value[c] = current[c];
140
+ out[c] = current[c];
141
+ }
142
+ return pixel;
143
+ }
144
+ let hx = history[0];
145
+ let hy = history[1];
146
+ let hz = history[2];
147
+ if (neighbourhood !== null) {
148
+ clipToNeighbourhood(CLIPPED, [hx, hy, hz], neighbourhood.min, neighbourhood.max);
149
+ hx = CLIPPED[0];
150
+ hy = CLIPPED[1];
151
+ hz = CLIPPED[2];
152
+ }
153
+ /*
154
+ * One over the count while a pixel is settling, so what comes out is the unbiased mean of
155
+ * everything it has seen rather than a blend biased toward whatever it started at — which is the
156
+ * difference between converging *to* the noisy mean and converging near it. The floor is what
157
+ * keeps a settled pixel able to notice the world changing; see `DENOISE_MIN_ALPHA`.
158
+ */
159
+ const samples = pixel.samples + 1;
160
+ const alpha = Math.max(1 / samples, DENOISE_MIN_ALPHA);
161
+ pixel.samples = samples;
162
+ const blended = [hx, hy, hz];
163
+ for (let c = 0; c < 3; c++) {
164
+ const value = blended[c] + (current[c] - blended[c]) * alpha;
165
+ pixel.value[c] = value;
166
+ out[c] = value;
167
+ }
168
+ return pixel;
169
+ }