@driftengine/core 3.62.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 +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 +70 -4
  9. package/dist/index.js +123 -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 +47 -14
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +128 -80
  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 +261 -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 +158 -88
  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,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
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The frame's declared distance fields — what the renderer's indirect light may be traced against.
3
+ *
4
+ * **A consumer declares this, and the seam is `addOccluder`'s for `addOccluder`'s reason.** A
5
+ * distance field is the shape of a thing it is *safe* to be traced against, and only the consumer
6
+ * knows which of its objects that is true of: the terrain and the walls, not the leaves, not the
7
+ * water, not the thing that is about to be deleted. `quality.occlusionCulling` already makes that
8
+ * sentence about occluders and culls nothing until somebody declares one. This makes it about
9
+ * light, and lights nothing until somebody declares a field.
10
+ *
11
+ * **A field and its placement are separate arguments on purpose.** Forty identical pillars are one
12
+ * baked field and forty matrices: the samples are megabytes and the matrices are bytes. So a source
13
+ * declared twice in a frame is two instances sharing one packed copy of its samples, and the queue
14
+ * is what knows the difference.
15
+ *
16
+ * **The transform is copied and the samples are not.** A consumer holds a matrix and moves it —
17
+ * that is what a matrix is for — so a queue that kept the reference would replay every field
18
+ * wearing the last pose written, which is `DecalQueue`'s recorded mistake and is the dangerous
19
+ * kind: a plausible picture rather than a broken one. A `FieldSource`, by contrast, is baked and
20
+ * does not move, and copying its samples once a frame would move the whole field across the bus to
21
+ * say the same thing.
22
+ *
23
+ * **Records are pooled and never released**, per `AGENTS.md`: `reset` returns the count to zero
24
+ * and keeps the storage, so a steady scene allocates on its first frame and never again.
25
+ */
26
+ import type { ReadonlyMat4 } from 'gl-matrix';
27
+ import type { FieldSource } from './globalField.ts';
28
+ /**
29
+ * How many placed fields one frame may declare.
30
+ *
31
+ * **Thirty-two, which is what `composeGlobalField` was measured against** — 707 ms on the CPU for
32
+ * thirty-two instances over four cascades, and the number the device composition has carried since.
33
+ * The cost is not in the count itself but in the instance buffer being sized once and bound for
34
+ * every cascade's dispatch, so it is a cap rather than a growth.
35
+ */
36
+ export declare const MAX_DISTANCE_FIELDS = 32;
37
+ /**
38
+ * How many samples the frame's distinct sources may add up to, across all of them.
39
+ *
40
+ * Four million floats is sixteen megabytes on the device, which is a 160-cubed field or two
41
+ * hundred 27-cubed ones. **The budget is over the distinct sources rather than the instances**,
42
+ * because that is what is actually uploaded.
43
+ */
44
+ export declare const MAX_DISTANCE_FIELD_SAMPLES: number;
45
+ /** One placed field. `transform` is this queue's own copy; `source` is the consumer's object. */
46
+ export interface DistanceFieldInstance {
47
+ /** The baked field. Shared by reference: it does not move and it is the large thing. */
48
+ source: FieldSource;
49
+ /** World from object, copied at the moment it was declared. Uniform scale only. */
50
+ readonly transform: Float32Array;
51
+ /**
52
+ * What colour this placement's surface is, copied like the transform. White where none was given.
53
+ *
54
+ * **A ray that lands on a wall needs to know what colour it is**, because what leaves a surface
55
+ * is the light arriving times its albedo — and a composed distance field is a union that has
56
+ * already forgotten which instance won. `demo/dev/bounce.html` is what made it necessary: a
57
+ * trace without it returned the same answer for a red wall and a white one.
58
+ *
59
+ * On the placement rather than the field, because the same baked shape is placed many times and
60
+ * two pillars cut from one mould may be painted differently.
61
+ */
62
+ readonly albedo: Float32Array;
63
+ }
64
+ export declare class DistanceFieldScene {
65
+ private readonly pool;
66
+ private count;
67
+ /** The distinct sources of this frame, in the order they were first declared. */
68
+ private readonly distinct;
69
+ /** Where each distinct source's samples start in the packed upload, in floats. */
70
+ private readonly offsets;
71
+ private samples;
72
+ /** The distinct sources of the frame that `reset` last ended, for `packingChanged`. */
73
+ private packed;
74
+ /** Set once when a budget is first reached, so a scene over it says so and then draws. */
75
+ private warnedInstances;
76
+ private warnedSamples;
77
+ /** How many placed fields this frame has recorded. */
78
+ get length(): number;
79
+ /** How many records are pooled, which a test uses to assert a steady scene stops allocating. */
80
+ get capacity(): number;
81
+ /** This frame's distinct sources, in the order their samples are packed. */
82
+ get sources(): readonly FieldSource[];
83
+ /** Floats the packed upload holds — the sum of the distinct sources' fields. */
84
+ get sampleCount(): number;
85
+ /**
86
+ * Whether the samples have to be uploaded again, or the placements alone will do.
87
+ *
88
+ * True when this frame's distinct sources are not the last frame's in the same order, since the
89
+ * offsets are positional. A scene whose fields never change answers false from its second frame
90
+ * on, which is what keeps a sixteen-megabyte upload out of the frame loop.
91
+ */
92
+ get packingChanged(): boolean;
93
+ /** Where a source's samples start in the packed upload, or -1 if it is not in this frame. */
94
+ offsetOf(source: FieldSource): number;
95
+ /**
96
+ * Declare a field, placed.
97
+ *
98
+ * **A source whose samples do not fit is refused whole rather than clipped.** Half a field on the
99
+ * device is not a coarser field; it is a field whose far half reads whatever the buffer held, and
100
+ * that composes as surfaces that are not there — the failure Wave 4A already paid for once, where
101
+ * a phantom surface passed 111,907 parity samples and was found by looking at a picture.
102
+ */
103
+ record(source: FieldSource, model: ReadonlyMat4, albedo?: ArrayLike<number>): void;
104
+ replay(each: (instance: DistanceFieldInstance) => void): void;
105
+ /** Empty it for the next frame, keeping the storage and remembering what was packed. */
106
+ reset(): void;
107
+ }
108
+ /**
109
+ * The world extent of everything a frame declared, into vectors the caller owns.
110
+ *
111
+ * **This is what a probe grid is fitted to when a scene declared none.** A renderer asked for
112
+ * indirect light has to put its probes somewhere, and the only description of the world it holds is
113
+ * the set of fields a consumer said its light may be traced against. That is also the *right*
114
+ * description: a grid sized to include everything drawn would spend its probes on the inside of the
115
+ * sky sphere, while a grid sized to the traceable geometry is a grid around the room.
116
+ *
117
+ * **Every corner is transformed, not the box.** A rotated box's axis-aligned extent is decided by
118
+ * where its corners went; transforming the two extremes and taking their min and max gives a box
119
+ * that is too small wherever a rotation is not a multiple of a quarter turn, and too small is a
120
+ * probe grid that stops short of the wall it is meant to surround.
121
+ *
122
+ * False when the frame declared nothing, rather than an empty box at the origin — which
123
+ * `fitProbeGrid` would answer with a plausible grid of one standing in a place no scene chose.
124
+ */
125
+ export declare function distanceFieldBounds(scene: DistanceFieldScene, min: Float32Array, max: Float32Array): boolean;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * The frame's declared distance fields — what the renderer's indirect light may be traced against.
3
+ *
4
+ * **A consumer declares this, and the seam is `addOccluder`'s for `addOccluder`'s reason.** A
5
+ * distance field is the shape of a thing it is *safe* to be traced against, and only the consumer
6
+ * knows which of its objects that is true of: the terrain and the walls, not the leaves, not the
7
+ * water, not the thing that is about to be deleted. `quality.occlusionCulling` already makes that
8
+ * sentence about occluders and culls nothing until somebody declares one. This makes it about
9
+ * light, and lights nothing until somebody declares a field.
10
+ *
11
+ * **A field and its placement are separate arguments on purpose.** Forty identical pillars are one
12
+ * baked field and forty matrices: the samples are megabytes and the matrices are bytes. So a source
13
+ * declared twice in a frame is two instances sharing one packed copy of its samples, and the queue
14
+ * is what knows the difference.
15
+ *
16
+ * **The transform is copied and the samples are not.** A consumer holds a matrix and moves it —
17
+ * that is what a matrix is for — so a queue that kept the reference would replay every field
18
+ * wearing the last pose written, which is `DecalQueue`'s recorded mistake and is the dangerous
19
+ * kind: a plausible picture rather than a broken one. A `FieldSource`, by contrast, is baked and
20
+ * does not move, and copying its samples once a frame would move the whole field across the bus to
21
+ * say the same thing.
22
+ *
23
+ * **Records are pooled and never released**, per `AGENTS.md`: `reset` returns the count to zero
24
+ * and keeps the storage, so a steady scene allocates on its first frame and never again.
25
+ */
26
+ import { assertCubicVoxels } from './globalField.js';
27
+ /**
28
+ * How many placed fields one frame may declare.
29
+ *
30
+ * **Thirty-two, which is what `composeGlobalField` was measured against** — 707 ms on the CPU for
31
+ * thirty-two instances over four cascades, and the number the device composition has carried since.
32
+ * The cost is not in the count itself but in the instance buffer being sized once and bound for
33
+ * every cascade's dispatch, so it is a cap rather than a growth.
34
+ */
35
+ export const MAX_DISTANCE_FIELDS = 32;
36
+ /**
37
+ * How many samples the frame's distinct sources may add up to, across all of them.
38
+ *
39
+ * Four million floats is sixteen megabytes on the device, which is a 160-cubed field or two
40
+ * hundred 27-cubed ones. **The budget is over the distinct sources rather than the instances**,
41
+ * because that is what is actually uploaded.
42
+ */
43
+ export const MAX_DISTANCE_FIELD_SAMPLES = 1 << 22;
44
+ function newRecord() {
45
+ return {
46
+ source: EMPTY_SOURCE,
47
+ transform: new Float32Array(16),
48
+ albedo: Float32Array.from([1, 1, 1]),
49
+ };
50
+ }
51
+ /** A placeholder a pooled record holds before its first use, so `source` is never null. */
52
+ const EMPTY_SOURCE = {
53
+ field: new Float32Array(0),
54
+ dims: [0, 0, 0],
55
+ bounds: new Float32Array(6),
56
+ };
57
+ export class DistanceFieldScene {
58
+ pool = [];
59
+ count = 0;
60
+ /** The distinct sources of this frame, in the order they were first declared. */
61
+ distinct = [];
62
+ /** Where each distinct source's samples start in the packed upload, in floats. */
63
+ offsets = new Map();
64
+ samples = 0;
65
+ /** The distinct sources of the frame that `reset` last ended, for `packingChanged`. */
66
+ packed = [];
67
+ /** Set once when a budget is first reached, so a scene over it says so and then draws. */
68
+ warnedInstances = false;
69
+ warnedSamples = false;
70
+ /** How many placed fields this frame has recorded. */
71
+ get length() {
72
+ return this.count;
73
+ }
74
+ /** How many records are pooled, which a test uses to assert a steady scene stops allocating. */
75
+ get capacity() {
76
+ return this.pool.length;
77
+ }
78
+ /** This frame's distinct sources, in the order their samples are packed. */
79
+ get sources() {
80
+ return this.distinct;
81
+ }
82
+ /** Floats the packed upload holds — the sum of the distinct sources' fields. */
83
+ get sampleCount() {
84
+ return this.samples;
85
+ }
86
+ /**
87
+ * Whether the samples have to be uploaded again, or the placements alone will do.
88
+ *
89
+ * True when this frame's distinct sources are not the last frame's in the same order, since the
90
+ * offsets are positional. A scene whose fields never change answers false from its second frame
91
+ * on, which is what keeps a sixteen-megabyte upload out of the frame loop.
92
+ */
93
+ get packingChanged() {
94
+ if (this.distinct.length !== this.packed.length)
95
+ return true;
96
+ for (let i = 0; i < this.distinct.length; i += 1) {
97
+ if (this.distinct[i] !== this.packed[i])
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ /** Where a source's samples start in the packed upload, or -1 if it is not in this frame. */
103
+ offsetOf(source) {
104
+ return this.offsets.get(source) ?? -1;
105
+ }
106
+ /**
107
+ * Declare a field, placed.
108
+ *
109
+ * **A source whose samples do not fit is refused whole rather than clipped.** Half a field on the
110
+ * device is not a coarser field; it is a field whose far half reads whatever the buffer held, and
111
+ * that composes as surfaces that are not there — the failure Wave 4A already paid for once, where
112
+ * a phantom surface passed 111,907 parity samples and was found by looking at a picture.
113
+ */
114
+ record(source, model, albedo) {
115
+ /*
116
+ * **Before anything else, because a malformed source is not a budget.** The two warnings below
117
+ * are a frame asking for more than it can carry and dropping the surplus is the honest answer;
118
+ * there is none to a source the composer cannot read. The renderer composes on the device and
119
+ * never reaches `composeGlobalField`, so its own check protects only the reference path.
120
+ */
121
+ assertCubicVoxels(source);
122
+ if (this.count >= MAX_DISTANCE_FIELDS) {
123
+ if (!this.warnedInstances) {
124
+ this.warnedInstances = true;
125
+ console.warn(`driftengine: more than ${MAX_DISTANCE_FIELDS} distance fields were declared in one ` +
126
+ 'frame, so the rest are not traced against. Fields that never move are cheaper baked ' +
127
+ 'into one source, which is composed as a single instance.');
128
+ }
129
+ return;
130
+ }
131
+ const known = this.offsets.get(source);
132
+ if (known === undefined) {
133
+ if (this.samples + source.field.length > MAX_DISTANCE_FIELD_SAMPLES) {
134
+ if (!this.warnedSamples) {
135
+ this.warnedSamples = true;
136
+ console.warn(`driftengine: the frame's distance fields exceed ${MAX_DISTANCE_FIELD_SAMPLES} ` +
137
+ 'samples, so this one is not traced against at all. A field composed in part reads ' +
138
+ 'whatever the buffer held beyond it, which is surfaces that are not there.');
139
+ }
140
+ return;
141
+ }
142
+ this.offsets.set(source, this.samples);
143
+ this.distinct.push(source);
144
+ this.samples += source.field.length;
145
+ }
146
+ let record = this.pool[this.count];
147
+ if (record === undefined) {
148
+ record = newRecord();
149
+ this.pool.push(record);
150
+ }
151
+ record.source = source;
152
+ for (let at = 0; at < 16; at += 1)
153
+ record.transform[at] = model[at] ?? 0;
154
+ /* Copied like the transform, and white where none was declared. */
155
+ for (let at = 0; at < 3; at += 1)
156
+ record.albedo[at] = albedo?.[at] ?? 1;
157
+ this.count += 1;
158
+ }
159
+ replay(each) {
160
+ for (let i = 0; i < this.count; i += 1) {
161
+ const record = this.pool[i];
162
+ if (record !== undefined)
163
+ each(record);
164
+ }
165
+ }
166
+ /** Empty it for the next frame, keeping the storage and remembering what was packed. */
167
+ reset() {
168
+ this.packed = this.distinct.slice();
169
+ this.count = 0;
170
+ this.distinct.length = 0;
171
+ this.offsets.clear();
172
+ this.samples = 0;
173
+ }
174
+ }
175
+ /**
176
+ * The world extent of everything a frame declared, into vectors the caller owns.
177
+ *
178
+ * **This is what a probe grid is fitted to when a scene declared none.** A renderer asked for
179
+ * indirect light has to put its probes somewhere, and the only description of the world it holds is
180
+ * the set of fields a consumer said its light may be traced against. That is also the *right*
181
+ * description: a grid sized to include everything drawn would spend its probes on the inside of the
182
+ * sky sphere, while a grid sized to the traceable geometry is a grid around the room.
183
+ *
184
+ * **Every corner is transformed, not the box.** A rotated box's axis-aligned extent is decided by
185
+ * where its corners went; transforming the two extremes and taking their min and max gives a box
186
+ * that is too small wherever a rotation is not a multiple of a quarter turn, and too small is a
187
+ * probe grid that stops short of the wall it is meant to surround.
188
+ *
189
+ * False when the frame declared nothing, rather than an empty box at the origin — which
190
+ * `fitProbeGrid` would answer with a plausible grid of one standing in a place no scene chose.
191
+ */
192
+ export function distanceFieldBounds(scene, min, max) {
193
+ if (scene.length === 0)
194
+ return false;
195
+ for (let axis = 0; axis < 3; axis += 1) {
196
+ min[axis] = Infinity;
197
+ max[axis] = -Infinity;
198
+ }
199
+ scene.replay((instance) => {
200
+ const box = instance.source.bounds;
201
+ const m = instance.transform;
202
+ for (let corner = 0; corner < 8; corner += 1) {
203
+ const x = box[(corner & 1) === 0 ? 0 : 3];
204
+ const y = box[(corner & 2) === 0 ? 1 : 4];
205
+ const z = box[(corner & 4) === 0 ? 2 : 5];
206
+ for (let axis = 0; axis < 3; axis += 1) {
207
+ const value = m[axis] * x +
208
+ m[4 + axis] * y +
209
+ m[8 + axis] * z +
210
+ m[12 + axis];
211
+ if (value < min[axis])
212
+ min[axis] = value;
213
+ if (value > max[axis])
214
+ max[axis] = value;
215
+ }
216
+ }
217
+ });
218
+ return true;
219
+ }