@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,172 @@
1
+ /**
2
+ * Whole meshes out of view, dropped before anything reads their clusters.
3
+ *
4
+ * **An instance here is a mesh**: every cluster names one through `meshOf`, and the raster and the
5
+ * shading both place it by `transforms[mesh]`. A mesh wholly outside the view has nothing to
6
+ * contribute to the frame, and before this every one of its clusters was still cut, tested against
7
+ * six planes and a cone, and occlusion-tested in phase two — correct, and the cost this stage
8
+ * removes.
9
+ *
10
+ * **The flag is read by the cut**, `LOD_CUT_WGSL`, because that is the one per-cluster stage with
11
+ * room for it: the cluster cull binds seven storage buffers and the compaction seven, of the eight a
12
+ * stage may bind by default, and the lookup needs two more (the flags and the cluster's mesh). A
13
+ * cluster the cut does not select is one the cluster cull then skips without testing and the
14
+ * compaction never lists — so a hidden mesh costs a lookup per cluster, and so does every cluster
15
+ * at a level the cut did not choose, which used to be tested for nothing.
16
+ *
17
+ * **The spheres are fitted once, at upload**, from the clusters' world spheres `clusterWorld.ts`
18
+ * already produced, for the reason that file gives: this pipeline's geometry does not move.
19
+ */
20
+ import { CLUSTER_CULL_FLOATS } from './cullClusters.js';
21
+ import { sphereOutsideFrustum } from './frustum.js';
22
+ import { clusterSelected } from './lodCut.js';
23
+ /** Four floats a mesh: world centre and radius. `CULL_INSTANCES_WGSL` reads this stride. */
24
+ export const INSTANCE_FLOATS = 4;
25
+ /** What the instance cull writes for a mesh wholly outside the view. Zero is everything else. */
26
+ export const INSTANCE_HIDDEN = 1;
27
+ /** Four words a cluster in the uploaded meta: index offset, index count, identifier, mesh. */
28
+ export const CLUSTER_META_WORDS = 4;
29
+ const META_MESH = 3;
30
+ /** Six floats a cluster in the cut's input, as `clusterWorld.ts` lays out `lod`. */
31
+ const LOD_FLOATS = 6;
32
+ /**
33
+ * Each mesh's world bounding sphere, from the world spheres of its clusters.
34
+ *
35
+ * **Held to the numbers the device reads.** The centre is the middle of the clusters' box, stored
36
+ * in single precision first; the radius is then measured from the *stored* centre and rounded up,
37
+ * never to nearest — a sphere rounded the wrong way is a few parts in ten million short of a
38
+ * cluster's far edge, and a cull that is not conservative is a hole in the picture.
39
+ *
40
+ * A mesh no cluster names is a point at the origin, which draws nothing whatever the cull says. A
41
+ * cluster naming a mesh past `meshCount` is refused: it would be bounded by nothing and drawn by a
42
+ * transform that does not exist.
43
+ */
44
+ export function instanceBounds(cull, meshOf, count, meshCount) {
45
+ const low = new Float64Array(meshCount * 3).fill(Infinity);
46
+ const high = new Float64Array(meshCount * 3).fill(-Infinity);
47
+ for (let c = 0; c < count; c += 1) {
48
+ const mesh = meshOf[c];
49
+ if (!(mesh < meshCount)) {
50
+ throw new RangeError(`instanceBounds: cluster ${String(c)} names mesh ${String(mesh)} and the scene has ${String(meshCount)} meshes`);
51
+ }
52
+ const at = c * CLUSTER_CULL_FLOATS;
53
+ const radius = cull[at + 3];
54
+ for (let axis = 0; axis < 3; axis += 1) {
55
+ const centre = cull[at + axis];
56
+ low[mesh * 3 + axis] = Math.min(low[mesh * 3 + axis], centre - radius);
57
+ high[mesh * 3 + axis] = Math.max(high[mesh * 3 + axis], centre + radius);
58
+ }
59
+ }
60
+ const out = new Float32Array(meshCount * INSTANCE_FLOATS);
61
+ for (let mesh = 0; mesh < meshCount; mesh += 1) {
62
+ if (!(low[mesh * 3] <= high[mesh * 3]))
63
+ continue;
64
+ for (let axis = 0; axis < 3; axis += 1) {
65
+ out[mesh * INSTANCE_FLOATS + axis] =
66
+ (low[mesh * 3 + axis] + high[mesh * 3 + axis]) / 2;
67
+ }
68
+ }
69
+ const reach = new Float64Array(meshCount);
70
+ for (let c = 0; c < count; c += 1) {
71
+ const mesh = meshOf[c];
72
+ const at = c * CLUSTER_CULL_FLOATS;
73
+ const from = mesh * INSTANCE_FLOATS;
74
+ const distance = Math.hypot(cull[at] - out[from], cull[at + 1] - out[from + 1], cull[at + 2] - out[from + 2]);
75
+ reach[mesh] = Math.max(reach[mesh], distance + cull[at + 3]);
76
+ }
77
+ for (let mesh = 0; mesh < meshCount; mesh += 1) {
78
+ const wanted = reach[mesh];
79
+ let radius = Math.fround(wanted);
80
+ /* Up, not to nearest: one step past the rounding is the smallest sphere that still holds. */
81
+ if (radius < wanted)
82
+ radius = Math.fround(wanted * (1 + 2 ** -23));
83
+ out[mesh * INSTANCE_FLOATS + 3] = radius;
84
+ }
85
+ return out;
86
+ }
87
+ /**
88
+ * One mesh's bounding sphere, from a contiguous run of its clusters' world bounds.
89
+ *
90
+ * **A run rather than a `meshOf` scan, because a streamed mesh's clusters are contiguous by
91
+ * construction** — the allocator hands out a range — so the scan `instanceBounds` does is work a
92
+ * streaming caller has already done.
93
+ *
94
+ * **A mesh with no clusters gets a sphere of zero radius rather than an empty box's arithmetic.**
95
+ * `Infinity - (-Infinity)` is `-Infinity`, and every comparison against `NaN` is false, which
96
+ * reads as a mesh that is never culled rather than one that is never drawn.
97
+ *
98
+ * The two forms must agree, and `instances.test.ts` is what holds them to it.
99
+ */
100
+ export function instanceSphereInto(cull, from, count, out, at) {
101
+ const base = at * INSTANCE_FLOATS;
102
+ if (count <= 0) {
103
+ out[base] = 0;
104
+ out[base + 1] = 0;
105
+ out[base + 2] = 0;
106
+ out[base + 3] = 0;
107
+ return;
108
+ }
109
+ const low = [Infinity, Infinity, Infinity];
110
+ const high = [-Infinity, -Infinity, -Infinity];
111
+ for (let c = from; c < from + count; c += 1) {
112
+ const record = c * CLUSTER_CULL_FLOATS;
113
+ const radius = cull[record + 3];
114
+ for (let axis = 0; axis < 3; axis += 1) {
115
+ const centre = cull[record + axis];
116
+ low[axis] = Math.min(low[axis], centre - radius);
117
+ high[axis] = Math.max(high[axis], centre + radius);
118
+ }
119
+ }
120
+ for (let axis = 0; axis < 3; axis += 1) {
121
+ out[base + axis] = (low[axis] + high[axis]) / 2;
122
+ }
123
+ /* The reach from that centre, which is what makes the sphere hold every cluster rather than
124
+ every corner of the box — the same second pass `instanceBounds` makes, for the same reason. */
125
+ let reach = 0;
126
+ for (let c = from; c < from + count; c += 1) {
127
+ const record = c * CLUSTER_CULL_FLOATS;
128
+ const distance = Math.hypot(cull[record] - out[base], cull[record + 1] - out[base + 1], cull[record + 2] - out[base + 2]);
129
+ reach = Math.max(reach, distance + cull[record + 3]);
130
+ }
131
+ let radius = Math.fround(reach);
132
+ /* Up, not to nearest: one step past the rounding is the smallest sphere that still holds. */
133
+ if (radius < reach)
134
+ radius = Math.fround(reach * (1 + 2 ** -23));
135
+ out[base + 3] = radius;
136
+ }
137
+ /**
138
+ * One flag a mesh: `INSTANCE_HIDDEN` where its sphere is wholly outside the view. Returns how many
139
+ * are. Mirrors `CULL_INSTANCES_WGSL`, which `gpu-parity.mjs` holds to `sphereOutsideFrustum`.
140
+ */
141
+ export function cullInstances(planes, spheres, count, out) {
142
+ let hidden = 0;
143
+ for (let i = 0; i < count; i += 1) {
144
+ const at = i * INSTANCE_FLOATS;
145
+ const outside = sphereOutsideFrustum(planes, spheres[at], spheres[at + 1], spheres[at + 2], spheres[at + 3]);
146
+ out[i] = outside ? INSTANCE_HIDDEN : 0;
147
+ if (outside)
148
+ hidden += 1;
149
+ }
150
+ return hidden;
151
+ }
152
+ /**
153
+ * The cut, over every cluster, with the instance flags applied first: one flag a cluster, and how
154
+ * many were selected. Mirrors `LOD_CUT_WGSL`.
155
+ */
156
+ export function selectClusters(lod, meta, hidden, screenHeight, fovY, thresholdPixels, eyeX, eyeY, eyeZ, out) {
157
+ const count = Math.floor(lod.length / LOD_FLOATS);
158
+ let chosen = 0;
159
+ for (let i = 0; i < count; i += 1) {
160
+ const mesh = meta[i * CLUSTER_META_WORDS + META_MESH];
161
+ if ((hidden[mesh] ?? 0) !== 0) {
162
+ out[i] = 0;
163
+ continue;
164
+ }
165
+ const at = i * LOD_FLOATS;
166
+ const selected = clusterSelected(lod[at + 4], lod[at + 5], Math.hypot(lod[at] - eyeX, lod[at + 1] - eyeY, lod[at + 2] - eyeZ), lod[at + 3], screenHeight, fovY, thresholdPixels);
167
+ out[i] = selected ? 1 : 0;
168
+ if (selected)
169
+ chosen += 1;
170
+ }
171
+ return chosen;
172
+ }
@@ -0,0 +1,165 @@
1
+ /** What the second pipeline's shading pass computes, in TypeScript, so something can disagree. */
2
+ /**
3
+ * **The lit expression had no reference until 2026-09-16, and that was a gap rather than a policy.**
4
+ *
5
+ * `shadeBins.ts` is the reference for the *reconstruction* — screen vertices, perspective-correct
6
+ * weights, their gradients — and `gpu-parity.mjs` runs it against `SHADE_SURFACE_WGSL` over
7
+ * generated triangles. The shading pass's header gives the reason and it is a good one: the
8
+ * interpolation is the only part that differs between the two pipelines, so it is the only part
9
+ * that can drift.
10
+ *
11
+ * That argument holds for a lit expression of four lines. It stops holding the moment the
12
+ * expression grows a term, and it was never an argument for having *no* reference: nothing in the
13
+ * tree computed what the shader computed, so nothing could disagree with it. The forward path's own
14
+ * expression is GLSL and cannot be compared to WGSL by reading them side by side.
15
+ *
16
+ * **This is the twin, and it is deliberately not the forward path's whole material.** What the
17
+ * second pipeline draws is still a subset — no texture, so every property a texel could vary is a
18
+ * per-material constant — and every term that *is* here matches `flat/main.ts` exactly. Stating
19
+ * the subset is the point; discovering it from a capture is what this repository keeps refusing.
20
+ */
21
+ /** A surface, after the visibility buffer has been turned back into one. */
22
+ export interface LitSurface {
23
+ /** Vertex colour times the material's tint. */
24
+ readonly albedo: readonly [number, number, number];
25
+ /** Unit length, in world space. */
26
+ readonly normal: readonly [number, number, number];
27
+ /** Unit length, from the surface toward the eye. */
28
+ readonly toEye: readonly [number, number, number];
29
+ /** 0 is a mirror, 1 is fully rough. */
30
+ readonly roughness: number;
31
+ /** How much of the light the surface returns as a highlight. Zero for a matte one. */
32
+ readonly specular: number;
33
+ /** Added as `emissiveColour * emissive`, which is `albedo * emissive` where no map says otherwise. */
34
+ readonly emissive: number;
35
+ /**
36
+ * What the glow is coloured by: flat/main.ts's `emissiveTint * emissiveMapped`, the albedo times
37
+ * an emissive map. **Defaults to the albedo**, so a surface with no map glows exactly as it did —
38
+ * and it reaches the glow and nothing else, because a map says where light is emitted rather than
39
+ * what the surface is made of.
40
+ */
41
+ readonly emissiveColour?: readonly [number, number, number];
42
+ /**
43
+ * How much of the environment the surface returns, before the angle and the roughness are
44
+ * accounted for. **Defaults to 0**, so a material that says nothing reflects nothing.
45
+ */
46
+ readonly reflectivity?: number;
47
+ /**
48
+ * How much of the directional source reaches this surface: 0 in full shadow, 1 in full light.
49
+ *
50
+ * **Applied to the diffuse term and the highlight, and to nothing else**, which is where
51
+ * `flat/main.ts` puts its own `sunShade`. Its comment records what applying it more widely costs:
52
+ * scale the ambient too and an enclosed face is darkened once for having no sun and again for the
53
+ * light it should still have received, which a consumer answered by raising a floor constant and
54
+ * losing the contrast permanently.
55
+ *
56
+ * **Defaults to 1**, so a caller that says nothing about occlusion renders exactly the frame this
57
+ * pipeline rendered before it had a shadow map — the same promise `roughness` and `specular`
58
+ * make.
59
+ */
60
+ readonly shade?: number;
61
+ /**
62
+ * 0 is a dielectric and 1 is a metal. **Defaults to 0.**
63
+ *
64
+ * `flat/main.ts` reads this per texel from the blue channel of an occlusion-roughness-metallic
65
+ * image; this pipeline has no textures, so it is one number a material. That is the whole of the
66
+ * difference — every expression it enters is the forward path's with a constant standing in for
67
+ * a fetch — and it is four of them: the direct diffuse it removes, the colour it gives the
68
+ * highlight, the side of the environment blend the highlight is added on, and the `f0` and the
69
+ * amount `ibl.ts` builds the weight from.
70
+ */
71
+ readonly metalness?: number;
72
+ /**
73
+ * How much of the surface's light a crevice leaves, from an ORM map's red channel. **Defaults
74
+ * to 1.**
75
+ *
76
+ * **It multiplies the whole result**, highlight, reflection and emissive included, because that
77
+ * is what `flat/main.ts` does — `lit *= ormOcclusion` — and its comment records the narrower
78
+ * reading as considered and not taken, with the double count against a cast shadow as the
79
+ * stated cost.
80
+ */
81
+ readonly occlusion?: number;
82
+ }
83
+ /** The frame's one directional light and its hemispheric fill. */
84
+ export interface LitEnvironment {
85
+ /** Toward the light, unit length, as `Environment.directionalDir` is. */
86
+ readonly lightDir: readonly [number, number, number];
87
+ readonly lightColour: readonly [number, number, number];
88
+ readonly sky: readonly [number, number, number];
89
+ readonly ground: readonly [number, number, number];
90
+ /**
91
+ * The room's diffuse light in the surface's own direction, from a cosine-convolved level.
92
+ *
93
+ * **It replaces the hemispheric gradient rather than lifting it**, by `irradianceAmount`, which
94
+ * is what `flat/main.ts` does and what its own comment records the alternative costing: a mix
95
+ * *toward* a coarse box-filtered level is a sample of the room rather than an integral over it,
96
+ * and it rendered a night courtyard black.
97
+ */
98
+ readonly irradiance?: readonly [number, number, number];
99
+ /** How much of the gradient the irradiance replaces. **Defaults to 0**, which keeps it. */
100
+ readonly irradianceAmount?: number;
101
+ /**
102
+ * What the surface sees in the mirror direction, from the chain at its own roughness.
103
+ *
104
+ * **It replaces a hemispheric gradient rather than standing alone**, by `radianceAmount`, and the
105
+ * default matters more here than anywhere else in this interface: a metal's environment weight is
106
+ * exactly 1 at every angle and every roughness, so a metal with nothing to reflect is **black**.
107
+ * The gradient is what `flat/main.ts` reflects where a scene has no probe, and computing it here
108
+ * rather than in the caller is what stops that being something a caller can forget.
109
+ */
110
+ readonly radiance?: readonly [number, number, number];
111
+ /**
112
+ * How much of the gradient the radiance replaces. **Defaults to 0**, which keeps the gradient —
113
+ * the same shape and the same default as `irradianceAmount`.
114
+ */
115
+ readonly radianceAmount?: number;
116
+ /**
117
+ * Whether that radiance came from a GGX-convolved chain. **Defaults to 0.**
118
+ *
119
+ * The split sum is the correct share of a *prefiltered* environment and asserts an integral that
120
+ * was never performed over anything else — `ibl.ts` carries what ignoring that costs.
121
+ */
122
+ readonly prefiltered?: number;
123
+ /**
124
+ * The frame's emissive gain times its night factor. **Defaults to 1.**
125
+ *
126
+ * `flat/main.ts` scales every glow by `uEmissiveGain * uNightFactor`, so a scene whose clock says
127
+ * it is day has nothing glowing on the forward path. This pipeline added `albedo * emissive` and
128
+ * nothing else until 2026-09-17, which is a rig that glowed on one pipeline and not the other.
129
+ */
130
+ readonly emission?: number;
131
+ }
132
+ /**
133
+ * How much of the sky a normal sees, against how much of the ground.
134
+ *
135
+ * `n.y * 0.5 + 0.5`, which is `flat/main.ts`'s own. Named rather than inlined because two
136
+ * pipelines disagreeing about it is two pipelines lighting one scene differently, with nothing on
137
+ * screen to say which is right.
138
+ */
139
+ export declare function HEMISPHERIC(ny: number): number;
140
+ /**
141
+ * The normalised GGX distribution, transcribed from `flat/lobes.ts`.
142
+ *
143
+ * **It peaks at one whatever the roughness**, and that is what makes a widened lobe broader rather
144
+ * than brighter — `lobes.ts` records paying for the version that did not. The floor on `a` is what
145
+ * keeps a mirror from dividing by nothing.
146
+ */
147
+ export declare function specularLobe(ndh: number, roughness: number): number;
148
+ export declare function newLitColour(): Float32Array;
149
+ /**
150
+ * The colour the second pipeline writes for one reconstructed surface.
151
+ *
152
+ * **The highlight is added on both sides of the environment blend, and which side carries how much
153
+ * is metalness.** `flat/main.ts` writes it as two lines with the blend between them, and its
154
+ * comments carry what each is for. The dielectric's share goes in *underneath*, where the blend
155
+ * dims it by the surface's own reflectance — moving it out "takes a lobe that was correctly dimmed
156
+ * and puts it back at full strength", which on a near-black glass lens carrying `specular` 1 is a
157
+ * white blob where there was none. The metal's share goes *over the top*, because on a metal the
158
+ * blend reaches 1 and a highlight left underneath is multiplied by exactly zero: the surface with
159
+ * the most reason to show a specular streak is the one guaranteed not to.
160
+ *
161
+ * **This file had both shares on the outside until 2026-09-17** and cited the metal argument for
162
+ * it. Nothing caught it: no material in any rig carries a `specular` and a `reflectivity` at once,
163
+ * so the two orders had never been asked to differ.
164
+ */
165
+ export declare function litColour(surface: LitSurface, environment: LitEnvironment, out: Float32Array): Float32Array;
@@ -0,0 +1,165 @@
1
+ /** What the second pipeline's shading pass computes, in TypeScript, so something can disagree. */
2
+ import { EMISSIVE_SHADOW_SHARE, MIN_LOBE_ALPHA } from '../shaders/flat/lobes.js';
3
+ import { environmentWeight } from './ibl.js';
4
+ /**
5
+ * How much of the sky a normal sees, against how much of the ground.
6
+ *
7
+ * `n.y * 0.5 + 0.5`, which is `flat/main.ts`'s own. Named rather than inlined because two
8
+ * pipelines disagreeing about it is two pipelines lighting one scene differently, with nothing on
9
+ * screen to say which is right.
10
+ */
11
+ export function HEMISPHERIC(ny) {
12
+ return ny * 0.5 + 0.5;
13
+ }
14
+ /**
15
+ * The normalised GGX distribution, transcribed from `flat/lobes.ts`.
16
+ *
17
+ * **It peaks at one whatever the roughness**, and that is what makes a widened lobe broader rather
18
+ * than brighter — `lobes.ts` records paying for the version that did not. The floor on `a` is what
19
+ * keeps a mirror from dividing by nothing.
20
+ */
21
+ /*
22
+ * **Two floors, and only one of them can be seen in the output.** The denominator's is what keeps
23
+ * a near-mirror's peak finite, and deleting it is worth 256 times the highlight at roughness 0.05 —
24
+ * `gpu-parity.mjs` catches that, now that a fifth of its corpus sits on the peak. The floor on `a`
25
+ * cannot be caught, because the denominator's already prevents the division by zero it guards
26
+ * against: at roughness zero the whole term is a ten-thousandth with it and zero without, and both
27
+ * round to nothing beside a lit surface. It is kept because it is what `flat/lobes.ts` has, and two
28
+ * spellings of one lobe differing by a guard is the drift this pair exists to prevent.
29
+ */
30
+ export function specularLobe(ndh, roughness) {
31
+ const a = Math.max(roughness * roughness, MIN_LOBE_ALPHA);
32
+ const a2 = a * a;
33
+ const d = ndh * ndh * (a2 - 1) + 1;
34
+ return (a2 * a2) / Math.max(d * d, 1e-8);
35
+ }
36
+ export function newLitColour() {
37
+ return new Float32Array(3);
38
+ }
39
+ /** Scratch, because this is per pixel in the reference as well as on the device. */
40
+ const HALFWAY = new Float32Array(3);
41
+ /**
42
+ * The colour the second pipeline writes for one reconstructed surface.
43
+ *
44
+ * **The highlight is added on both sides of the environment blend, and which side carries how much
45
+ * is metalness.** `flat/main.ts` writes it as two lines with the blend between them, and its
46
+ * comments carry what each is for. The dielectric's share goes in *underneath*, where the blend
47
+ * dims it by the surface's own reflectance — moving it out "takes a lobe that was correctly dimmed
48
+ * and puts it back at full strength", which on a near-black glass lens carrying `specular` 1 is a
49
+ * white blob where there was none. The metal's share goes *over the top*, because on a metal the
50
+ * blend reaches 1 and a highlight left underneath is multiplied by exactly zero: the surface with
51
+ * the most reason to show a specular streak is the one guaranteed not to.
52
+ *
53
+ * **This file had both shares on the outside until 2026-09-17** and cited the metal argument for
54
+ * it. Nothing caught it: no material in any rig carries a `specular` and a `reflectivity` at once,
55
+ * so the two orders had never been asked to differ.
56
+ */
57
+ export function litColour(surface, environment, out) {
58
+ const [nx, ny, nz] = surface.normal;
59
+ const [lx, ly, lz] = environment.lightDir;
60
+ const ndl = Math.max(nx * lx + ny * ly + nz * lz, 0);
61
+ const fill = HEMISPHERIC(ny);
62
+ /* The half vector between the light and the eye, which is where a highlight is brightest. */
63
+ HALFWAY[0] = lx + surface.toEye[0];
64
+ HALFWAY[1] = ly + surface.toEye[1];
65
+ HALFWAY[2] = lz + surface.toEye[2];
66
+ const length = Math.hypot(HALFWAY[0], HALFWAY[1], HALFWAY[2]);
67
+ let ndh = 0;
68
+ let voh = 0;
69
+ if (length > 0) {
70
+ ndh = Math.max((nx * HALFWAY[0] + ny * HALFWAY[1] + nz * HALFWAY[2]) /
71
+ length, 0);
72
+ voh = Math.max((surface.toEye[0] * HALFWAY[0] +
73
+ surface.toEye[1] * HALFWAY[1] +
74
+ surface.toEye[2] * HALFWAY[2]) /
75
+ length, 0);
76
+ }
77
+ /*
78
+ * **Computed whether or not the surface is shiny, and multiplied by zero where it is not.** A
79
+ * branch on a material's specular would be a branch on a value read from a table, which is what
80
+ * the shading pass's own header refuses for the same reason the forward path does: it is not
81
+ * provably uniform control flow.
82
+ */
83
+ const lobe = specularLobe(ndh, surface.roughness);
84
+ /* One number for both direct terms: the sun being occluded is one fact about one source. */
85
+ const shade = surface.shade ?? 1;
86
+ const metal = surface.metalness ?? 0;
87
+ const occlusion = surface.occlusion ?? 1;
88
+ /*
89
+ * **A glow is scaled by the frame and dimmed by a share of the sun's shadow**, which is
90
+ * flat/main.ts's `mix(1, min(lightShade, sunShade), EMISSIVE_SHADOW_SHARE)` with no lamps: a lit
91
+ * inlay in a deck that is itself in shade, rather than a lamp head blacked out by a passer-by.
92
+ */
93
+ const glow = surface.emissive * (environment.emission ?? 1) * (1 + (shade - 1) * EMISSIVE_SHADOW_SHARE);
94
+ /*
95
+ * **A metal's highlight goes white at a grazing angle, and that is most of what reads as
96
+ * polished.** Schlick's reflectance climbs to 1 at the edge whatever the material is, so a red
97
+ * metal shows a red highlight face-on and a white one along every panel edge. Scaled by
98
+ * metalness, so `specColour + (1 - specColour) * 0` is `specColour` exactly on a dielectric.
99
+ */
100
+ const facing = 1 - voh;
101
+ const edge = facing * facing * facing * facing * facing * metal;
102
+ /*
103
+ * **How much of the room the surface returns, computed once rather than per channel.** It is a
104
+ * function of the angle, the roughness and the material alone — `ibl.ts` carries both halves of
105
+ * the split sum and why the selector between them is exactly 0 or exactly 1.
106
+ */
107
+ const nv = nx * surface.toEye[0] +
108
+ ny * surface.toEye[1] +
109
+ nz * surface.toEye[2];
110
+ const ndv = Math.max(nv, 0);
111
+ const weight = environmentWeight(ndv, surface.roughness, surface.reflectivity ?? 0, environment.prefiltered ?? 0, metal);
112
+ const irradianceAmount = environment.irradianceAmount ?? 0;
113
+ const radianceAmount = environment.radianceAmount ?? 0;
114
+ /*
115
+ * **The mirror direction, and what a surface sees along it with no probe bound.** `reflect(-v, n)`
116
+ * written out: the two-colour gradient the ambient comes from, sampled in the direction the
117
+ * surface is reflecting rather than the one it faces. That distinction is the whole of what makes
118
+ * a curved dielectric read as curved with no environment in the scene at all.
119
+ *
120
+ * **The unclamped dot, not `ndv`.** `reflect` takes the facing term as it is; clamping it at zero
121
+ * would fold the mirror direction back on itself for a surface seen from behind — which happens
122
+ * on every silhouette where interpolated normals turn past the eye — and reflect the sky where
123
+ * the ground belongs. The weight below clamps because a negative Fresnel is not a reflectance;
124
+ * the two uses are different questions about one dot product.
125
+ */
126
+ const mirroredY = 2 * nv * ny - surface.toEye[1];
127
+ for (let channel = 0; channel < 3; channel += 1) {
128
+ const albedo = surface.albedo[channel];
129
+ const hemispheric = environment.ground[channel] +
130
+ (environment.sky[channel] - environment.ground[channel]) * fill;
131
+ const room = environment.irradiance?.[channel] ?? 0;
132
+ const ambient = hemispheric + (room - hemispheric) * irradianceAmount;
133
+ /*
134
+ * **A metal's highlight takes its own colour and a dielectric's takes the light's**, which is
135
+ * why gold under a white lamp reads as gold rather than as beige plastic with a white dot on
136
+ * it. `specular` is this engine's dielectric reflectance — a look control rather than an `f0` —
137
+ * so the blend runs from it to the albedo rather than from 0.04.
138
+ */
139
+ const specColour = surface.specular + (albedo - surface.specular) * metal;
140
+ const sunSpec = specColour + (1 - specColour) * edge;
141
+ const highlight = environment.lightColour[channel] * lobe * sunSpec * shade;
142
+ /*
143
+ * **A metal keeps its ambient and loses its direct diffuse**, written as one term because the
144
+ * honest two cancel: a metal's reduced diffuse plus the ambient floor that stops a rough one
145
+ * going black once its diffuse is gone sum to `albedo * ambient` exactly.
146
+ */
147
+ const base = albedo *
148
+ (ambient + environment.lightColour[channel] * ndl * shade * (1 - metal)) +
149
+ highlight * (1 - metal);
150
+ /*
151
+ * **What a metal reflects is the room through itself**, which is the other half of "a metal is
152
+ * nothing but its reflection". `mix(1, albedo, 0)` is exactly 1, so a dielectric reflects the
153
+ * room's own colour as it always did.
154
+ */
155
+ const gradient = environment.ground[channel] +
156
+ (environment.sky[channel] - environment.ground[channel]) *
157
+ HEMISPHERIC(mirroredY);
158
+ const seen = gradient + ((environment.radiance?.[channel] ?? 0) - gradient) * radianceAmount;
159
+ const reflected = seen * (1 + (albedo - 1) * metal);
160
+ const glowColour = surface.emissiveColour?.[channel] ?? albedo;
161
+ out[channel] =
162
+ (base + (reflected - base) * weight + highlight * metal + glowColour * glow) * occlusion;
163
+ }
164
+ return out;
165
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Choosing which level of the cluster graph to draw, per cluster, independently.
3
+ *
4
+ * **Independently is the whole point.** A cluster asks only about its own error and its parent's,
5
+ * never about its neighbours — and the graph's monotonic-error guarantee is what makes the result
6
+ * crack-free anyway. That is why this can run per cluster on the device with no communication.
7
+ *
8
+ * **Both halves of the rule are needed.** "Draw it if its own error is small enough" alone draws
9
+ * every ancestor as well; "draw it if its parent's error is too large" alone draws nothing at the
10
+ * root, where the parent error is infinite and therefore always too large — which is exactly the
11
+ * case that makes the coarsest level appear and never disappear.
12
+ */
13
+ /**
14
+ * How large a geometric error appears on screen, in pixels.
15
+ *
16
+ * Distance is clamped below by the cluster's own radius, so a cluster the camera is inside reports
17
+ * an enormous error rather than dividing by something near zero — which is the difference between
18
+ * "always use the finest level here" and a non-finite number reaching a comparison.
19
+ */
20
+ export declare function projectedError(error: number, distance: number, radius: number, screenHeight: number, fovY: number): number;
21
+ export declare function distanceTo(cx: number, cy: number, cz: number, ex: number, ey: number, ez: number): number;
22
+ /**
23
+ * Whether this cluster is the one to draw.
24
+ *
25
+ * True when its own error is acceptable and its parent's is not — see the header for why both
26
+ * halves are required.
27
+ */
28
+ export declare function clusterSelected(ownError: number, parentError: number, distance: number, radius: number, screenHeight: number, fovY: number, thresholdPixels: number): boolean;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Choosing which level of the cluster graph to draw, per cluster, independently.
3
+ *
4
+ * **Independently is the whole point.** A cluster asks only about its own error and its parent's,
5
+ * never about its neighbours — and the graph's monotonic-error guarantee is what makes the result
6
+ * crack-free anyway. That is why this can run per cluster on the device with no communication.
7
+ *
8
+ * **Both halves of the rule are needed.** "Draw it if its own error is small enough" alone draws
9
+ * every ancestor as well; "draw it if its parent's error is too large" alone draws nothing at the
10
+ * root, where the parent error is infinite and therefore always too large — which is exactly the
11
+ * case that makes the coarsest level appear and never disappear.
12
+ */
13
+ /**
14
+ * How large a geometric error appears on screen, in pixels.
15
+ *
16
+ * Distance is clamped below by the cluster's own radius, so a cluster the camera is inside reports
17
+ * an enormous error rather than dividing by something near zero — which is the difference between
18
+ * "always use the finest level here" and a non-finite number reaching a comparison.
19
+ */
20
+ export function projectedError(error, distance, radius, screenHeight, fovY) {
21
+ const safe = Math.max(distance, radius, 1e-4);
22
+ const scale = screenHeight / (2 * Math.tan(fovY / 2));
23
+ return (error * scale) / safe;
24
+ }
25
+ export function distanceTo(cx, cy, cz, ex, ey, ez) {
26
+ return Math.hypot(cx - ex, cy - ey, cz - ez);
27
+ }
28
+ /**
29
+ * Whether this cluster is the one to draw.
30
+ *
31
+ * True when its own error is acceptable and its parent's is not — see the header for why both
32
+ * halves are required.
33
+ */
34
+ export function clusterSelected(ownError, parentError, distance, radius, screenHeight, fovY, thresholdPixels) {
35
+ const own = projectedError(ownError, distance, radius, screenHeight, fovY);
36
+ const parent = projectedError(parentError, distance, radius, screenHeight, fovY);
37
+ return own <= thresholdPixels && parent > thresholdPixels;
38
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Grouping the frame's pixels by the material that has to shade them.
3
+ *
4
+ * **A visibility buffer defers the shading and does not organise it.** Neighbouring pixels can
5
+ * hold triangles from different meshes with different materials, so shading it in place means
6
+ * every invocation branching on a material it cannot know until it has read the buffer — which on
7
+ * a wide machine is every lane in a group taking every branch. Binning first turns that into one
8
+ * dispatch per material, each of which shades pixels that all want the same code.
9
+ *
10
+ * **A material with no pixels gets a zero-length bin and a zero-workgroup dispatch, never an
11
+ * absent one.** The arguments are indexed by material identifier, so dropping an empty one shifts
12
+ * every later material's block and the scene shades with the wrong code — a picture that is
13
+ * plausible, entirely wrong, and correlates with nothing a consumer changed. `binsPresent` is the
14
+ * separate question of how many materials actually appear, which is a number to report rather
15
+ * than a length to index by.
16
+ *
17
+ * **Offsets are a prefix sum and the cursors are a copy of it.** The scatter needs somewhere to
18
+ * count from that is not the offsets themselves, or the second pixel of a bin overwrites the
19
+ * first's offset and every later bin starts in the wrong place.
20
+ */
21
+ /** Invocations a shading group covers. One pixel each; the bins are lists, not rectangles. */
22
+ export declare const BIN_GROUP_SIZE = 64;
23
+ /** Words in one material's dispatch block, so a caller can size the buffer. */
24
+ export declare const BIN_DISPATCH_WORDS = 3;
25
+ /**
26
+ * Which material a pixel wants, or -1 where nothing covered it.
27
+ *
28
+ * **The sentinel test survives perturbation and is kept**, which is worth the sentence because it
29
+ * looks like the load-bearing line and is not. `VIS_EMPTY` is `0xffffffff`, so its cluster field
30
+ * reads as 33,554,431 — and the range test one line down refuses any cluster past the table, which
31
+ * every real table is far smaller than. Deleting either check alone therefore changes no answer
32
+ * this repository can produce. What the sentinel test decides is a table sized to the full
33
+ * twenty-five bits, which is 134 MB and nothing allocates; what the range test decides is a
34
+ * visibility buffer left over from a frame with more geometry in it, which is a real case and is
35
+ * the one `gpu-parity.mjs` generates. Both are kept because they answer different questions and
36
+ * one of them is cheap insurance against the other's assumption.
37
+ */
38
+ export declare function materialAt(visibility: Uint32Array, materialOf: Uint32Array, pixel: number): number;
39
+ /**
40
+ * How many pixels each material owns, and how many pixels were covered at all.
41
+ *
42
+ * `counts` is cleared here rather than by the caller, because a count buffer reused across frames
43
+ * without clearing grows without bound and the bins then run off the end of the pixel list —
44
+ * which appears as the shading reading a neighbouring bin's pixels.
45
+ */
46
+ export declare function countBins(visibility: Uint32Array, materialOf: Uint32Array, counts: Uint32Array): number;
47
+ /** Where each material's pixels begin: the exclusive prefix sum of the counts. */
48
+ export declare function binOffsets(counts: Uint32Array, offsets: Uint32Array): number;
49
+ /**
50
+ * Write every covered pixel's index into its material's slice of `pixels`.
51
+ *
52
+ * `cursors` starts as a copy of `offsets` and is advanced as the scatter runs, so `offsets` stays
53
+ * readable afterwards — which the shading dispatch needs, because it is what tells a group where
54
+ * its material's list starts.
55
+ */
56
+ export declare function fillBins(visibility: Uint32Array, materialOf: Uint32Array, offsets: Uint32Array, cursors: Uint32Array, pixels: Uint32Array): void;
57
+ /** One dispatch block per **declared** material, zero groups where the bin is empty. */
58
+ export declare function binDispatchArgs(counts: Uint32Array, out: Uint32Array, groupSize?: number): void;
59
+ /** How many materials the frame actually contains, which is not how many it declares. */
60
+ export declare function binsPresent(counts: Uint32Array): number;