@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,179 @@
1
+ /**
2
+ * The spatial operators on the device: convolution, its transpose, patch embedding, resizing and
3
+ * max pooling.
4
+ *
5
+ * **One invocation per output element, each gathering what it reads**, in the layouts of the
6
+ * references in `@driftengine/texture`'s `spatial.ts` — channel-major images and the upstream's
7
+ * weight layouts. A transposed convolution is written as a gather, not the reference's scatter, so
8
+ * no two invocations write one element; the two agree because each output sums the same products.
9
+ *
10
+ * **Resizing bakes the reference's conventions as constants**: the half-pixel source without aligned
11
+ * corners, clamped below zero for bilinear only, stepping by the node's own step where it gives one;
12
+ * the corner-to-corner scale with them, and zero for an output of one; and the cubic kernel at
13
+ * a = −0.75 with its taps clamped to the border.
14
+ *
15
+ * What it gives up: a gather per output reads its whole window from storage with no reuse between
16
+ * neighbours, which is the cost a tiled convolution removes. Measured against a DPT head's largest,
17
+ * a 3×3 of 32 channels at 518², it is 3.2 ms — 1.5 TFLOP/s where the tiled multiply reaches 4.5 on
18
+ * the same device (an RX 9070 XT) — so the decoder's convolutions are the second thing to tile.
19
+ */
20
+ import { float, num, perElement, read, size } from './kernelKit.js';
21
+ /* Grouped, output `o` reads the `perGroup` channels from `first`: its group's run of the input. */
22
+ const conv2d = (request) => {
23
+ const [, h, w] = request.inputShapes[0];
24
+ const [, perGroup, kh, kw] = request.inputShapes[1];
25
+ const [cout, oh, ow] = request.outputShape;
26
+ const outPerGroup = cout / num(request.attributes, 'groups', 1);
27
+ const stride = num(request.attributes, 'stride', 1);
28
+ const padding = num(request.attributes, 'padding', 0);
29
+ const bias = request.inputShapes.length > 2 ? read(request, 2, 'o') : '0.0';
30
+ return perElement(request, size(request.outputShape), ` let o = i / ${oh * ow}u;
31
+ let y = i32((i / ${ow}u) % ${oh}u);
32
+ let x = i32(i % ${ow}u);
33
+ let first = (o / ${outPerGroup}u) * ${perGroup}u;
34
+ var sum = ${bias};
35
+ for (var c = 0u; c < ${perGroup}u; c = c + 1u) {
36
+ for (var ky = 0; ky < ${kh}; ky = ky + 1) {
37
+ let sy = y * ${stride} - ${padding} + ky;
38
+ if (sy < 0 || sy >= ${h}) { continue; }
39
+ for (var kx = 0; kx < ${kw}; kx = kx + 1) {
40
+ let sx = x * ${stride} - ${padding} + kx;
41
+ if (sx < 0 || sx >= ${w}) { continue; }
42
+ sum = sum + ${read(request, 0, `((first + c) * ${h}u + u32(sy)) * ${w}u + u32(sx)`)} * ${read(request, 1, `((o * ${perGroup}u + c) * ${kh}u + u32(ky)) * ${kw}u + u32(kx)`)};
43
+ }
44
+ }
45
+ }
46
+ output[i] = sum;`);
47
+ };
48
+ const convTranspose2d = (request) => {
49
+ const [cin, h, w] = request.inputShapes[0];
50
+ const [, cout, kh, kw] = request.inputShapes[1];
51
+ const [, oh, ow] = request.outputShape;
52
+ const stride = num(request.attributes, 'stride', 1);
53
+ const padding = num(request.attributes, 'padding', 0);
54
+ const bias = request.inputShapes.length > 2 ? read(request, 2, 'o') : '0.0';
55
+ return perElement(request, size(request.outputShape), ` let o = i / ${oh * ow}u;
56
+ let ty = i32((i / ${ow}u) % ${oh}u);
57
+ let tx = i32(i % ${ow}u);
58
+ var sum = ${bias};
59
+ for (var ky = 0; ky < ${kh}; ky = ky + 1) {
60
+ let sy = ty + ${padding} - ky;
61
+ if (sy < 0 || sy % ${stride} != 0 || sy / ${stride} >= ${h}) { continue; }
62
+ for (var kx = 0; kx < ${kw}; kx = kx + 1) {
63
+ let sx = tx + ${padding} - kx;
64
+ if (sx < 0 || sx % ${stride} != 0 || sx / ${stride} >= ${w}) { continue; }
65
+ for (var c = 0u; c < ${cin}u; c = c + 1u) {
66
+ sum = sum + ${read(request, 0, `(c * ${h}u + u32(sy / ${stride})) * ${w}u + u32(sx / ${stride})`)} * ${read(request, 1, `((c * ${cout}u + o) * ${kh}u + u32(ky)) * ${kw}u + u32(kx)`)};
67
+ }
68
+ }
69
+ }
70
+ output[i] = sum;`);
71
+ };
72
+ const patchEmbed = (request) => {
73
+ const [cin, h, w] = request.inputShapes[0];
74
+ const dim = request.inputShapes[1][0];
75
+ const patch = num(request.attributes, 'patch');
76
+ const cols = Math.floor(w / patch);
77
+ const bias = request.inputShapes.length > 2 ? read(request, 2, 'o') : '0.0';
78
+ return perElement(request, size(request.outputShape), ` let token = i / ${dim}u;
79
+ let o = i % ${dim}u;
80
+ let top = (token / ${cols}u) * ${patch}u;
81
+ let left = (token % ${cols}u) * ${patch}u;
82
+ var sum = ${bias};
83
+ for (var c = 0u; c < ${cin}u; c = c + 1u) {
84
+ for (var ky = 0u; ky < ${patch}u; ky = ky + 1u) {
85
+ for (var kx = 0u; kx < ${patch}u; kx = kx + 1u) {
86
+ sum = sum + ${read(request, 0, `(c * ${h}u + top + ky) * ${w}u + left + kx`)} * ${read(request, 1, `((o * ${cin}u + c) * ${patch}u + ky) * ${patch}u + kx`)};
87
+ }
88
+ }
89
+ }
90
+ output[i] = sum;`);
91
+ };
92
+ /*
93
+ * The source coordinate of a destination index, as a WGSL expression, in the reference's
94
+ * convention: `step` source pixels a destination pixel, which is the sizes' ratio unless the node
95
+ * gives its own, as a resize by a scale factor does upstream.
96
+ */
97
+ function source(index, input, output, align, cubic, step) {
98
+ if (align)
99
+ return output <= 1 ? '0.0' : `f32(${index}) * ${float((input - 1) / (output - 1))}`;
100
+ const raw = `(f32(${index}) + 0.5) * ${float(step ?? input / output)} - 0.5`;
101
+ return cubic ? raw : `max(${raw}, 0.0)`;
102
+ }
103
+ /* PyTorch's `nearest`: `⌊i·scale⌋`, the product in single precision as the reference takes it. */
104
+ const nearest = (request) => {
105
+ const [, h, w] = request.inputShapes[0];
106
+ const [, oh, ow] = request.outputShape;
107
+ const stepped = typeof request.attributes['stepHeight'] === 'number';
108
+ const scaleY = stepped ? num(request.attributes, 'stepHeight') : h / oh;
109
+ const scaleX = stepped ? num(request.attributes, 'stepWidth') : w / ow;
110
+ return perElement(request, size(request.outputShape), ` let plane = (i / ${oh * ow}u) * ${h * w}u;
111
+ let sy = min(u32(floor(f32((i / ${ow}u) % ${oh}u) * ${float(scaleY)})), ${h - 1}u);
112
+ let sx = min(u32(floor(f32(i % ${ow}u) * ${float(scaleX)})), ${w - 1}u);
113
+ output[i] = ${read(request, 0, `plane + sy * ${w}u + sx`)};`);
114
+ };
115
+ const resize = (request) => {
116
+ if (request.attributes['mode'] === 'nearest')
117
+ return nearest(request);
118
+ const [, h, w] = request.inputShapes[0];
119
+ const [, oh, ow] = request.outputShape;
120
+ const align = request.attributes['alignCorners'] === true;
121
+ const cubic = request.attributes['mode'] === 'bicubic';
122
+ const stepped = typeof request.attributes['stepHeight'] === 'number';
123
+ const stepHeight = stepped ? num(request.attributes, 'stepHeight') : undefined;
124
+ const stepWidth = stepped ? num(request.attributes, 'stepWidth') : undefined;
125
+ const at = (y, x) => read(request, 0, `plane + u32(clamp(${y}, 0, ${h - 1})) * ${w}u + u32(clamp(${x}, 0, ${w - 1}))`);
126
+ const sample = cubic
127
+ ? ` let a = -0.75;
128
+ let tx = sx - floor(sx);
129
+ let ty = sy - floor(sy);
130
+ let wx = array<f32, 4>(cubicFar(tx + 1.0, a), cubicNear(tx, a), cubicNear(1.0 - tx, a), cubicFar(2.0 - tx, a));
131
+ let wy = array<f32, 4>(cubicFar(ty + 1.0, a), cubicNear(ty, a), cubicNear(1.0 - ty, a), cubicFar(2.0 - ty, a));
132
+ var value = 0.0;
133
+ for (var j = 0; j < 4; j = j + 1) {
134
+ var across = 0.0;
135
+ for (var k = 0; k < 4; k = k + 1) { across = across + wx[k] * ${at('y0 - 1 + j', 'x0 - 1 + k')}; }
136
+ value = value + wy[j] * across;
137
+ }
138
+ output[i] = value;`
139
+ : ` let tx = sx - f32(x0);
140
+ let ty = sy - f32(y0);
141
+ let top = ${at('y0', 'x0')} * (1.0 - tx) + ${at('y0', 'x0 + 1')} * tx;
142
+ let bottom = ${at('y0 + 1', 'x0')} * (1.0 - tx) + ${at('y0 + 1', 'x0 + 1')} * tx;
143
+ output[i] = top * (1.0 - ty) + bottom * ty;`;
144
+ const kernel = perElement(request, size(request.outputShape), ` let plane = (i / ${oh * ow}u) * ${h * w}u;
145
+ let sy = ${source(`(i / ${ow}u) % ${oh}u`, h, oh, align, cubic, stepHeight)};
146
+ let sx = ${source(`i % ${ow}u`, w, ow, align, cubic, stepWidth)};
147
+ let y0 = i32(floor(sy));
148
+ let x0 = i32(floor(sx));
149
+ ${sample}`);
150
+ if (!cubic)
151
+ return kernel;
152
+ const helpers = `fn cubicNear(x: f32, a: f32) -> f32 { return ((a + 2.0) * x - (a + 3.0)) * x * x + 1.0; }
153
+ fn cubicFar(x: f32, a: f32) -> f32 { return ((a * x - 5.0 * a) * x + 8.0 * a) * x - 4.0 * a; }`;
154
+ return { ...kernel, code: kernel.code.replace('\n@compute', `\n${helpers}\n\n@compute`) };
155
+ };
156
+ /* The largest of each window; a window always lies inside the input, since there is no padding. */
157
+ const maxPool2d = (request) => {
158
+ const [, h, w] = request.inputShapes[0];
159
+ const [, oh, ow] = request.outputShape;
160
+ const kernel = num(request.attributes, 'kernel');
161
+ const stride = num(request.attributes, 'stride', kernel);
162
+ return perElement(request, size(request.outputShape), ` let plane = (i / ${oh * ow}u) * ${h * w}u;
163
+ let top = ((i / ${ow}u) % ${oh}u) * ${stride}u;
164
+ let left = (i % ${ow}u) * ${stride}u;
165
+ var largest = ${read(request, 0, `plane + top * ${w}u + left`)};
166
+ for (var ky = 0u; ky < ${kernel}u; ky = ky + 1u) {
167
+ for (var kx = 0u; kx < ${kernel}u; kx = kx + 1u) {
168
+ largest = max(largest, ${read(request, 0, `plane + (top + ky) * ${w}u + left + kx`)});
169
+ }
170
+ }
171
+ output[i] = largest;`);
172
+ };
173
+ export const SPATIAL_KERNELS = [
174
+ ['conv2d', conv2d],
175
+ ['convTranspose2d', convTranspose2d],
176
+ ['patchEmbed', patchEmbed],
177
+ ['resize', resize],
178
+ ['maxPool2d', maxPool2d],
179
+ ];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A cloud of Gaussians drawn and differentiated on the device, one workgroup per tile.
3
+ *
4
+ * **Hand-authored WGSL, which the 2026-08-12 rule makes the exception for a compute shader**: there
5
+ * is no second copy to keep in step, because a compute shader has no WebGL2 twin, and the safety
6
+ * property inverts — this *is* the bottom of its own stack. What stands in for the generator's
7
+ * check is `scripts/gpu-parity.mjs`, which runs every one of these against
8
+ * `@driftengine/capture`'s reference rasteriser on the same cloud.
9
+ *
10
+ * **Why a tile and not a splat.** Splats overlap and compositing is ordered, so one invocation per
11
+ * splat has no way for two of them writing one pixel to agree who was in front. Turned inside out —
12
+ * one workgroup per tile of the frame, one invocation per pixel, each walking the list of splats
13
+ * that touch its tile — every pixel accumulates alone in its own registers and nothing is contended.
14
+ *
15
+ * **The backward pass reduces rather than adds atomically, and that is a determinism decision.**
16
+ * Many pixels contribute to one splat's gradient. WGSL has no floating-point atomic, and the usual
17
+ * workaround — a compare-and-swap loop over the bits — sums in whatever order the lanes happen to
18
+ * arrive; floating-point addition is not associative, so the same cloud would give a different
19
+ * gradient every run. Instead the workgroup steps through its list together, and for each splat the
20
+ * 256 lanes are reduced in a fixed tree into one slot of its own, which `splatTiles.ts`'s `pairOf`
21
+ * allocates. A second pass sums each splat's contiguous run. Same answer, every run, every device.
22
+ *
23
+ * **The frame is written premultiplied, as the reference writes it**: three channels of colour
24
+ * already multiplied by the light that reached them, and the coverage beside it.
25
+ */
26
+ /**
27
+ * Numbers a splat occupies on the device: centre, screen covariance, opacity, colour, reach.
28
+ *
29
+ * **The reach is in the record because a tile is not a splat's footprint.** A workgroup walks every
30
+ * pixel of its tile, and a splat that touches the tile at one corner reaches only part of it — so
31
+ * without the same box the reference loops over, the device draws the splat at pixels the reference
32
+ * never evaluates. Measured before it was there: 6.7 · 10⁻³ of a channel on the worst pixel, which
33
+ * is not a rounding difference but a different picture. Just outside three standard deviations a
34
+ * Gaussian is still worth about a hundredth of its opacity, far above the faint cutoff.
35
+ */
36
+ export declare const SPLAT_RASTER_FLOATS = 10;
37
+ /** Numbers a splat's screen gradient occupies, in `@driftengine/capture`'s own order. */
38
+ export declare const SPLAT_GRADIENT_FLOATS = 9;
39
+ /** One workgroup covers a tile: the invocations are its pixels. */
40
+ export declare const SPLAT_RASTER_GROUP = 16;
41
+ /**
42
+ * The frame, and the light left at every pixel after the whole cloud — which the backward pass
43
+ * needs and cannot recover, because it walks the list the other way.
44
+ *
45
+ * Bindings: `params`, `splats`, `offsets`, `counts`, `lists`, `frame`, `light`.
46
+ */
47
+ export declare const SPLAT_FORWARD_WGSL = "\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> splats: array<f32>;\n@group(0) @binding(2) var<storage, read> offsets: array<u32>;\n@group(0) @binding(3) var<storage, read> counts: array<u32>;\n@group(0) @binding(4) var<storage, read> lists: array<u32>;\n@group(0) @binding(5) var<storage, read_write> frame: array<f32>;\n@group(0) @binding(6) var<storage, read_write> light: array<f32>;\n\nstruct Params {\n width: u32,\n height: u32,\n across: u32,\n splats: u32,\n};\n\nconst FAINT: f32 = 0.0009803921568627451;\nconst FLOATS: u32 = 10u;\nconst GRADS: u32 = 9u;\n\n\nstruct Splat {\n centre: vec2f,\n /* The screen covariance, [[a, b], [b, d]], the low-pass already in it. */\n a: f32,\n b: f32,\n d: f32,\n opacity: f32,\n colour: vec3f,\n /* Half the side of the pixel box it is drawn in, which is what bounds it. */\n reach: f32,\n};\n\nfn readSplat(index: u32) -> Splat {\n let at = index * FLOATS;\n var one: Splat;\n one.centre = vec2f(splats[at], splats[at + 1u]);\n one.a = splats[at + 2u];\n one.b = splats[at + 3u];\n one.d = splats[at + 4u];\n one.opacity = splats[at + 5u];\n one.colour = vec3f(splats[at + 6u], splats[at + 7u], splats[at + 8u]);\n one.reach = splats[at + 9u];\n return one;\n}\n\n/* The pixel box the binning bins by, asked of one pixel: the rasteriser's own bound. */\nfn insideBox(one: Splat, pixel: vec2f) -> bool {\n return pixel.x >= floor(one.centre.x - one.reach)\n && pixel.x <= ceil(one.centre.x + one.reach)\n && pixel.y >= floor(one.centre.y - one.reach)\n && pixel.y <= ceil(one.centre.y + one.reach);\n}\n\n/* The inverse of the screen covariance, which is what the exponent is written in. */\nfn conicOf(one: Splat) -> vec3f {\n let determinant = one.a * one.d - one.b * one.b;\n return vec3f(one.d / determinant, -one.b / determinant, one.a / determinant);\n}\n\n/* How much of a splat reaches a pixel, from the pixel's own centre. */\nfn weightAt(conic: vec3f, offset: vec2f) -> f32 {\n let power = -0.5 * (conic.x * offset.x * offset.x\n + 2.0 * conic.y * offset.x * offset.y\n + conic.z * offset.y * offset.y);\n return exp(power);\n}\n\n\n@compute @workgroup_size(16, 16, 1)\nfn main(@builtin(global_invocation_id) id: vec3u, @builtin(workgroup_id) group: vec3u) {\n if (id.x >= params.width || id.y >= params.height) { return; }\n let tile = group.y * params.across + group.x;\n let start = offsets[tile];\n let held = counts[tile];\n let centre = vec2f(f32(id.x) + 0.5, f32(id.y) + 0.5);\n\n var transmit = 1.0;\n var colour = vec3f(0.0);\n var covered = 0.0;\n for (var slot = 0u; slot < held; slot = slot + 1u) {\n let one = readSplat(lists[start + slot]);\n if (!insideBox(one, vec2f(f32(id.x), f32(id.y)))) { continue; }\n let alpha = one.opacity * weightAt(conicOf(one), centre - one.centre);\n if (alpha < FAINT) { continue; }\n let share = alpha * transmit;\n colour = colour + share * one.colour;\n covered = covered + share;\n transmit = transmit * (1.0 - alpha);\n }\n\n let pixel = id.y * params.width + id.x;\n frame[pixel * 4u] = colour.x;\n frame[pixel * 4u + 1u] = colour.y;\n frame[pixel * 4u + 2u] = colour.z;\n frame[pixel * 4u + 3u] = covered;\n light[pixel] = transmit;\n}\n";
48
+ /**
49
+ * The gradients of one tile's splats, each into a slot of its own.
50
+ *
51
+ * The workgroup walks its list furthest first, in step: every lane holds the light in front of the
52
+ * splat it has reached and the colour accumulated behind it, and after each splat the lanes are
53
+ * reduced in a fixed tree so the sum is the same on every device and every run.
54
+ *
55
+ * Bindings: `params`, `splats`, `offsets`, `counts`, `lists`, `pairOf`, `dPixels`, `light`,
56
+ * `partials`.
57
+ */
58
+ export declare const SPLAT_BACKWARD_WGSL = "\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> splats: array<f32>;\n@group(0) @binding(2) var<storage, read> offsets: array<u32>;\n@group(0) @binding(3) var<storage, read> counts: array<u32>;\n@group(0) @binding(4) var<storage, read> lists: array<u32>;\n@group(0) @binding(5) var<storage, read> pairOf: array<u32>;\n@group(0) @binding(6) var<storage, read> dPixels: array<f32>;\n@group(0) @binding(7) var<storage, read> light: array<f32>;\n@group(0) @binding(8) var<storage, read_write> partials: array<f32>;\n\nstruct Params {\n width: u32,\n height: u32,\n across: u32,\n splats: u32,\n};\n\nconst FAINT: f32 = 0.0009803921568627451;\nconst FLOATS: u32 = 10u;\nconst GRADS: u32 = 9u;\n\n\nstruct Splat {\n centre: vec2f,\n /* The screen covariance, [[a, b], [b, d]], the low-pass already in it. */\n a: f32,\n b: f32,\n d: f32,\n opacity: f32,\n colour: vec3f,\n /* Half the side of the pixel box it is drawn in, which is what bounds it. */\n reach: f32,\n};\n\nfn readSplat(index: u32) -> Splat {\n let at = index * FLOATS;\n var one: Splat;\n one.centre = vec2f(splats[at], splats[at + 1u]);\n one.a = splats[at + 2u];\n one.b = splats[at + 3u];\n one.d = splats[at + 4u];\n one.opacity = splats[at + 5u];\n one.colour = vec3f(splats[at + 6u], splats[at + 7u], splats[at + 8u]);\n one.reach = splats[at + 9u];\n return one;\n}\n\n/* The pixel box the binning bins by, asked of one pixel: the rasteriser's own bound. */\nfn insideBox(one: Splat, pixel: vec2f) -> bool {\n return pixel.x >= floor(one.centre.x - one.reach)\n && pixel.x <= ceil(one.centre.x + one.reach)\n && pixel.y >= floor(one.centre.y - one.reach)\n && pixel.y <= ceil(one.centre.y + one.reach);\n}\n\n/* The inverse of the screen covariance, which is what the exponent is written in. */\nfn conicOf(one: Splat) -> vec3f {\n let determinant = one.a * one.d - one.b * one.b;\n return vec3f(one.d / determinant, -one.b / determinant, one.a / determinant);\n}\n\n/* How much of a splat reaches a pixel, from the pixel's own centre. */\nfn weightAt(conic: vec3f, offset: vec2f) -> f32 {\n let power = -0.5 * (conic.x * offset.x * offset.x\n + 2.0 * conic.y * offset.x * offset.y\n + conic.z * offset.y * offset.y);\n return exp(power);\n}\n\n\nconst LANES: u32 = 256u;\nvar<workgroup> gathered: array<array<f32, 9>, 256>;\n\n@compute @workgroup_size(16, 16, 1)\nfn main(\n @builtin(global_invocation_id) id: vec3u,\n @builtin(workgroup_id) group: vec3u,\n @builtin(local_invocation_index) lane: u32,\n) {\n let tile = group.y * params.across + group.x;\n let start = offsets[tile];\n let held = counts[tile];\n /* Off the edge of the frame there is no pixel, and the lane still meets every barrier below. */\n let inside = id.x < params.width && id.y < params.height;\n let pixel = select(0u, id.y * params.width + id.x, inside);\n let centre = vec2f(f32(id.x) + 0.5, f32(id.y) + 0.5);\n\n var transmit = select(1.0, light[pixel], inside);\n var behind = vec3f(0.0);\n var behindCover = 0.0;\n var dPixel = vec4f(0.0);\n if (inside) {\n dPixel = vec4f(dPixels[pixel * 4u], dPixels[pixel * 4u + 1u],\n dPixels[pixel * 4u + 2u], dPixels[pixel * 4u + 3u]);\n }\n\n for (var step = 0u; step < held; step = step + 1u) {\n let slot = held - 1u - step;\n let index = lists[start + slot];\n let one = readSplat(index);\n let conic = conicOf(one);\n let offset = centre - one.centre;\n let weight = weightAt(conic, offset);\n let alpha = one.opacity * weight;\n let contributes = inside && alpha >= FAINT\n && insideBox(one, vec2f(f32(id.x), f32(id.y)));\n\n var mine: array<f32, GRADS>;\n for (var k = 0u; k < GRADS; k = k + 1u) { mine[k] = 0.0; }\n if (contributes) {\n /* Undo this splat's own share to recover the light that reached it. */\n /* Divided by what is left of the light, and by one where nothing is. */\n let left = select(1.0 - alpha, 1.0, alpha == 1.0);\n let before = transmit / left;\n var dAlpha = 0.0;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let grad = dPixel[c];\n mine[6u + c] = grad * alpha * before;\n dAlpha = dAlpha + grad * before * (one.colour[c] - behind[c]);\n }\n dAlpha = dAlpha + dPixel.w * before * (1.0 - behindCover);\n mine[5u] = dAlpha * weight;\n\n /* Through the exponent to the centre and the covariance, as the reference has it. */\n let dPower = dAlpha * one.opacity * weight;\n mine[0u] = dPower * (conic.x * offset.x + conic.y * offset.y);\n mine[1u] = dPower * (conic.y * offset.x + conic.z * offset.y);\n let dInverse = vec3f(\n -0.5 * dPower * offset.x * offset.x,\n -0.5 * dPower * offset.x * offset.y,\n -0.5 * dPower * offset.y * offset.y);\n mine[2u] = -(conic.x * dInverse.x * conic.x + 2.0 * conic.x * conic.y * dInverse.y\n + conic.y * dInverse.z * conic.y);\n mine[3u] = -2.0 * (conic.x * dInverse.x * conic.y + conic.y * dInverse.y * conic.y\n + conic.x * dInverse.y * conic.z + conic.y * dInverse.z * conic.z);\n mine[4u] = -(conic.y * dInverse.x * conic.y + 2.0 * conic.y * conic.z * dInverse.y\n + conic.z * dInverse.z * conic.z);\n\n /* And carry this splat into what stands behind the next one. */\n behind = alpha * one.colour + (1.0 - alpha) * behind;\n behindCover = alpha + (1.0 - alpha) * behindCover;\n transmit = before;\n }\n\n /* The lanes, reduced in a fixed tree: the same sum in the same order on every device. */\n for (var k = 0u; k < GRADS; k = k + 1u) { gathered[lane][k] = mine[k]; }\n workgroupBarrier();\n for (var half = LANES / 2u; half > 0u; half = half / 2u) {\n if (lane < half) {\n for (var k = 0u; k < GRADS; k = k + 1u) {\n gathered[lane][k] = gathered[lane][k] + gathered[lane + half][k];\n }\n }\n workgroupBarrier();\n }\n if (lane == 0u) {\n let pair = pairOf[start + slot] * GRADS;\n for (var k = 0u; k < GRADS; k = k + 1u) { partials[pair + k] = gathered[0][k]; }\n }\n workgroupBarrier();\n }\n}\n";
59
+ /**
60
+ * Each splat's own run of partial gradients, summed in index order.
61
+ *
62
+ * Bindings: `params`, `pairOffsets`, `tilesPerSplat`, `partials`, `gradients`.
63
+ */
64
+ export declare const SPLAT_REDUCE_WGSL = "\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> pairOffsets: array<u32>;\n@group(0) @binding(2) var<storage, read> tilesPerSplat: array<u32>;\n@group(0) @binding(3) var<storage, read> partials: array<f32>;\n@group(0) @binding(4) var<storage, read_write> gradients: array<f32>;\n\nstruct Params {\n width: u32,\n height: u32,\n across: u32,\n splats: u32,\n};\n\nconst FAINT: f32 = 0.0009803921568627451;\nconst FLOATS: u32 = 10u;\nconst GRADS: u32 = 9u;\n\n\n\n@compute @workgroup_size(64, 1, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let splat = id.x;\n if (splat >= params.splats) { return; }\n let start = pairOffsets[splat];\n let held = tilesPerSplat[splat];\n for (var k = 0u; k < GRADS; k = k + 1u) {\n var total = 0.0;\n for (var slot = 0u; slot < held; slot = slot + 1u) {\n total = total + partials[(start + slot) * GRADS + k];\n }\n gradients[splat * GRADS + k] = total;\n }\n}\n";
65
+ /**
66
+ * One Adam step over one family of parameters.
67
+ *
68
+ * The bias corrections arrive already computed, because they are a running product on the host —
69
+ * `β₁ⁿ` raised on the device would be a second definition of the same decay and would drift from
70
+ * the reference's in the last bits for no gain.
71
+ *
72
+ * Bindings: `settings` (count, rate, correction1, correction2), `values`, `gradient`, `moment`,
73
+ * `second`.
74
+ */
75
+ export declare const SPLAT_ADAM_WGSL = "\nstruct Settings {\n count: u32,\n rate: f32,\n correction1: f32,\n correction2: f32,\n};\n\nconst BETA1: f32 = 0.9;\nconst BETA2: f32 = 0.999;\nconst EPSILON: f32 = 1e-8;\n\n@group(0) @binding(0) var<uniform> settings: Settings;\n@group(0) @binding(1) var<storage, read_write> values: array<f32>;\n@group(0) @binding(2) var<storage, read> gradient: array<f32>;\n@group(0) @binding(3) var<storage, read_write> moment: array<f32>;\n@group(0) @binding(4) var<storage, read_write> second: array<f32>;\n\n@compute @workgroup_size(64, 1, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let at = id.x;\n if (at >= settings.count) { return; }\n let g = gradient[at];\n let m = BETA1 * moment[at] + (1.0 - BETA1) * g;\n let v = BETA2 * second[at] + (1.0 - BETA2) * g * g;\n moment[at] = m;\n second[at] = v;\n values[at] = values[at]\n - (settings.rate * (m / settings.correction1)) / (sqrt(v / settings.correction2) + EPSILON);\n}\n";
@@ -0,0 +1,337 @@
1
+ /**
2
+ * A cloud of Gaussians drawn and differentiated on the device, one workgroup per tile.
3
+ *
4
+ * **Hand-authored WGSL, which the 2026-08-12 rule makes the exception for a compute shader**: there
5
+ * is no second copy to keep in step, because a compute shader has no WebGL2 twin, and the safety
6
+ * property inverts — this *is* the bottom of its own stack. What stands in for the generator's
7
+ * check is `scripts/gpu-parity.mjs`, which runs every one of these against
8
+ * `@driftengine/capture`'s reference rasteriser on the same cloud.
9
+ *
10
+ * **Why a tile and not a splat.** Splats overlap and compositing is ordered, so one invocation per
11
+ * splat has no way for two of them writing one pixel to agree who was in front. Turned inside out —
12
+ * one workgroup per tile of the frame, one invocation per pixel, each walking the list of splats
13
+ * that touch its tile — every pixel accumulates alone in its own registers and nothing is contended.
14
+ *
15
+ * **The backward pass reduces rather than adds atomically, and that is a determinism decision.**
16
+ * Many pixels contribute to one splat's gradient. WGSL has no floating-point atomic, and the usual
17
+ * workaround — a compare-and-swap loop over the bits — sums in whatever order the lanes happen to
18
+ * arrive; floating-point addition is not associative, so the same cloud would give a different
19
+ * gradient every run. Instead the workgroup steps through its list together, and for each splat the
20
+ * 256 lanes are reduced in a fixed tree into one slot of its own, which `splatTiles.ts`'s `pairOf`
21
+ * allocates. A second pass sums each splat's contiguous run. Same answer, every run, every device.
22
+ *
23
+ * **The frame is written premultiplied, as the reference writes it**: three channels of colour
24
+ * already multiplied by the light that reached them, and the coverage beside it.
25
+ */
26
+ /**
27
+ * Numbers a splat occupies on the device: centre, screen covariance, opacity, colour, reach.
28
+ *
29
+ * **The reach is in the record because a tile is not a splat's footprint.** A workgroup walks every
30
+ * pixel of its tile, and a splat that touches the tile at one corner reaches only part of it — so
31
+ * without the same box the reference loops over, the device draws the splat at pixels the reference
32
+ * never evaluates. Measured before it was there: 6.7 · 10⁻³ of a channel on the worst pixel, which
33
+ * is not a rounding difference but a different picture. Just outside three standard deviations a
34
+ * Gaussian is still worth about a hundredth of its opacity, far above the faint cutoff.
35
+ */
36
+ export const SPLAT_RASTER_FLOATS = 10;
37
+ /** Numbers a splat's screen gradient occupies, in `@driftengine/capture`'s own order. */
38
+ export const SPLAT_GRADIENT_FLOATS = 9;
39
+ /** One workgroup covers a tile: the invocations are its pixels. */
40
+ export const SPLAT_RASTER_GROUP = 16;
41
+ /** Below this a splat contributes less than a quarter of a value of an eight-bit channel. */
42
+ const FAINT = '0.0009803921568627451';
43
+ /** What every pass needs: the frame's shape and the two record widths. */
44
+ const SHARED = /* wgsl */ `
45
+ struct Params {
46
+ width: u32,
47
+ height: u32,
48
+ across: u32,
49
+ splats: u32,
50
+ };
51
+
52
+ const FAINT: f32 = ${FAINT};
53
+ const FLOATS: u32 = ${SPLAT_RASTER_FLOATS}u;
54
+ const GRADS: u32 = ${SPLAT_GRADIENT_FLOATS}u;
55
+
56
+ `;
57
+ /** Reading a splat and evaluating it, for the two passes that touch the cloud itself. */
58
+ const SPLAT_READER = /* wgsl */ `
59
+ struct Splat {
60
+ centre: vec2f,
61
+ /* The screen covariance, [[a, b], [b, d]], the low-pass already in it. */
62
+ a: f32,
63
+ b: f32,
64
+ d: f32,
65
+ opacity: f32,
66
+ colour: vec3f,
67
+ /* Half the side of the pixel box it is drawn in, which is what bounds it. */
68
+ reach: f32,
69
+ };
70
+
71
+ fn readSplat(index: u32) -> Splat {
72
+ let at = index * FLOATS;
73
+ var one: Splat;
74
+ one.centre = vec2f(splats[at], splats[at + 1u]);
75
+ one.a = splats[at + 2u];
76
+ one.b = splats[at + 3u];
77
+ one.d = splats[at + 4u];
78
+ one.opacity = splats[at + 5u];
79
+ one.colour = vec3f(splats[at + 6u], splats[at + 7u], splats[at + 8u]);
80
+ one.reach = splats[at + 9u];
81
+ return one;
82
+ }
83
+
84
+ /* The pixel box the binning bins by, asked of one pixel: the rasteriser's own bound. */
85
+ fn insideBox(one: Splat, pixel: vec2f) -> bool {
86
+ return pixel.x >= floor(one.centre.x - one.reach)
87
+ && pixel.x <= ceil(one.centre.x + one.reach)
88
+ && pixel.y >= floor(one.centre.y - one.reach)
89
+ && pixel.y <= ceil(one.centre.y + one.reach);
90
+ }
91
+
92
+ /* The inverse of the screen covariance, which is what the exponent is written in. */
93
+ fn conicOf(one: Splat) -> vec3f {
94
+ let determinant = one.a * one.d - one.b * one.b;
95
+ return vec3f(one.d / determinant, -one.b / determinant, one.a / determinant);
96
+ }
97
+
98
+ /* How much of a splat reaches a pixel, from the pixel's own centre. */
99
+ fn weightAt(conic: vec3f, offset: vec2f) -> f32 {
100
+ let power = -0.5 * (conic.x * offset.x * offset.x
101
+ + 2.0 * conic.y * offset.x * offset.y
102
+ + conic.z * offset.y * offset.y);
103
+ return exp(power);
104
+ }
105
+ `;
106
+ /** The two together, which is what the tiled passes include. */
107
+ const COMMON = SHARED + SPLAT_READER;
108
+ /**
109
+ * The frame, and the light left at every pixel after the whole cloud — which the backward pass
110
+ * needs and cannot recover, because it walks the list the other way.
111
+ *
112
+ * Bindings: `params`, `splats`, `offsets`, `counts`, `lists`, `frame`, `light`.
113
+ */
114
+ export const SPLAT_FORWARD_WGSL = /* wgsl */ `
115
+ @group(0) @binding(0) var<uniform> params: Params;
116
+ @group(0) @binding(1) var<storage, read> splats: array<f32>;
117
+ @group(0) @binding(2) var<storage, read> offsets: array<u32>;
118
+ @group(0) @binding(3) var<storage, read> counts: array<u32>;
119
+ @group(0) @binding(4) var<storage, read> lists: array<u32>;
120
+ @group(0) @binding(5) var<storage, read_write> frame: array<f32>;
121
+ @group(0) @binding(6) var<storage, read_write> light: array<f32>;
122
+ ${COMMON}
123
+
124
+ @compute @workgroup_size(${SPLAT_RASTER_GROUP}, ${SPLAT_RASTER_GROUP}, 1)
125
+ fn main(@builtin(global_invocation_id) id: vec3u, @builtin(workgroup_id) group: vec3u) {
126
+ if (id.x >= params.width || id.y >= params.height) { return; }
127
+ let tile = group.y * params.across + group.x;
128
+ let start = offsets[tile];
129
+ let held = counts[tile];
130
+ let centre = vec2f(f32(id.x) + 0.5, f32(id.y) + 0.5);
131
+
132
+ var transmit = 1.0;
133
+ var colour = vec3f(0.0);
134
+ var covered = 0.0;
135
+ for (var slot = 0u; slot < held; slot = slot + 1u) {
136
+ let one = readSplat(lists[start + slot]);
137
+ if (!insideBox(one, vec2f(f32(id.x), f32(id.y)))) { continue; }
138
+ let alpha = one.opacity * weightAt(conicOf(one), centre - one.centre);
139
+ if (alpha < FAINT) { continue; }
140
+ let share = alpha * transmit;
141
+ colour = colour + share * one.colour;
142
+ covered = covered + share;
143
+ transmit = transmit * (1.0 - alpha);
144
+ }
145
+
146
+ let pixel = id.y * params.width + id.x;
147
+ frame[pixel * 4u] = colour.x;
148
+ frame[pixel * 4u + 1u] = colour.y;
149
+ frame[pixel * 4u + 2u] = colour.z;
150
+ frame[pixel * 4u + 3u] = covered;
151
+ light[pixel] = transmit;
152
+ }
153
+ `;
154
+ /**
155
+ * The gradients of one tile's splats, each into a slot of its own.
156
+ *
157
+ * The workgroup walks its list furthest first, in step: every lane holds the light in front of the
158
+ * splat it has reached and the colour accumulated behind it, and after each splat the lanes are
159
+ * reduced in a fixed tree so the sum is the same on every device and every run.
160
+ *
161
+ * Bindings: `params`, `splats`, `offsets`, `counts`, `lists`, `pairOf`, `dPixels`, `light`,
162
+ * `partials`.
163
+ */
164
+ export const SPLAT_BACKWARD_WGSL = /* wgsl */ `
165
+ @group(0) @binding(0) var<uniform> params: Params;
166
+ @group(0) @binding(1) var<storage, read> splats: array<f32>;
167
+ @group(0) @binding(2) var<storage, read> offsets: array<u32>;
168
+ @group(0) @binding(3) var<storage, read> counts: array<u32>;
169
+ @group(0) @binding(4) var<storage, read> lists: array<u32>;
170
+ @group(0) @binding(5) var<storage, read> pairOf: array<u32>;
171
+ @group(0) @binding(6) var<storage, read> dPixels: array<f32>;
172
+ @group(0) @binding(7) var<storage, read> light: array<f32>;
173
+ @group(0) @binding(8) var<storage, read_write> partials: array<f32>;
174
+ ${COMMON}
175
+
176
+ const LANES: u32 = ${SPLAT_RASTER_GROUP * SPLAT_RASTER_GROUP}u;
177
+ var<workgroup> gathered: array<array<f32, ${SPLAT_GRADIENT_FLOATS}>, ${SPLAT_RASTER_GROUP * SPLAT_RASTER_GROUP}>;
178
+
179
+ @compute @workgroup_size(${SPLAT_RASTER_GROUP}, ${SPLAT_RASTER_GROUP}, 1)
180
+ fn main(
181
+ @builtin(global_invocation_id) id: vec3u,
182
+ @builtin(workgroup_id) group: vec3u,
183
+ @builtin(local_invocation_index) lane: u32,
184
+ ) {
185
+ let tile = group.y * params.across + group.x;
186
+ let start = offsets[tile];
187
+ let held = counts[tile];
188
+ /* Off the edge of the frame there is no pixel, and the lane still meets every barrier below. */
189
+ let inside = id.x < params.width && id.y < params.height;
190
+ let pixel = select(0u, id.y * params.width + id.x, inside);
191
+ let centre = vec2f(f32(id.x) + 0.5, f32(id.y) + 0.5);
192
+
193
+ var transmit = select(1.0, light[pixel], inside);
194
+ var behind = vec3f(0.0);
195
+ var behindCover = 0.0;
196
+ var dPixel = vec4f(0.0);
197
+ if (inside) {
198
+ dPixel = vec4f(dPixels[pixel * 4u], dPixels[pixel * 4u + 1u],
199
+ dPixels[pixel * 4u + 2u], dPixels[pixel * 4u + 3u]);
200
+ }
201
+
202
+ for (var step = 0u; step < held; step = step + 1u) {
203
+ let slot = held - 1u - step;
204
+ let index = lists[start + slot];
205
+ let one = readSplat(index);
206
+ let conic = conicOf(one);
207
+ let offset = centre - one.centre;
208
+ let weight = weightAt(conic, offset);
209
+ let alpha = one.opacity * weight;
210
+ let contributes = inside && alpha >= FAINT
211
+ && insideBox(one, vec2f(f32(id.x), f32(id.y)));
212
+
213
+ var mine: array<f32, GRADS>;
214
+ for (var k = 0u; k < GRADS; k = k + 1u) { mine[k] = 0.0; }
215
+ if (contributes) {
216
+ /* Undo this splat's own share to recover the light that reached it. */
217
+ /* Divided by what is left of the light, and by one where nothing is. */
218
+ let left = select(1.0 - alpha, 1.0, alpha == 1.0);
219
+ let before = transmit / left;
220
+ var dAlpha = 0.0;
221
+ for (var c = 0u; c < 3u; c = c + 1u) {
222
+ let grad = dPixel[c];
223
+ mine[6u + c] = grad * alpha * before;
224
+ dAlpha = dAlpha + grad * before * (one.colour[c] - behind[c]);
225
+ }
226
+ dAlpha = dAlpha + dPixel.w * before * (1.0 - behindCover);
227
+ mine[5u] = dAlpha * weight;
228
+
229
+ /* Through the exponent to the centre and the covariance, as the reference has it. */
230
+ let dPower = dAlpha * one.opacity * weight;
231
+ mine[0u] = dPower * (conic.x * offset.x + conic.y * offset.y);
232
+ mine[1u] = dPower * (conic.y * offset.x + conic.z * offset.y);
233
+ let dInverse = vec3f(
234
+ -0.5 * dPower * offset.x * offset.x,
235
+ -0.5 * dPower * offset.x * offset.y,
236
+ -0.5 * dPower * offset.y * offset.y);
237
+ mine[2u] = -(conic.x * dInverse.x * conic.x + 2.0 * conic.x * conic.y * dInverse.y
238
+ + conic.y * dInverse.z * conic.y);
239
+ mine[3u] = -2.0 * (conic.x * dInverse.x * conic.y + conic.y * dInverse.y * conic.y
240
+ + conic.x * dInverse.y * conic.z + conic.y * dInverse.z * conic.z);
241
+ mine[4u] = -(conic.y * dInverse.x * conic.y + 2.0 * conic.y * conic.z * dInverse.y
242
+ + conic.z * dInverse.z * conic.z);
243
+
244
+ /* And carry this splat into what stands behind the next one. */
245
+ behind = alpha * one.colour + (1.0 - alpha) * behind;
246
+ behindCover = alpha + (1.0 - alpha) * behindCover;
247
+ transmit = before;
248
+ }
249
+
250
+ /* The lanes, reduced in a fixed tree: the same sum in the same order on every device. */
251
+ for (var k = 0u; k < GRADS; k = k + 1u) { gathered[lane][k] = mine[k]; }
252
+ workgroupBarrier();
253
+ for (var half = LANES / 2u; half > 0u; half = half / 2u) {
254
+ if (lane < half) {
255
+ for (var k = 0u; k < GRADS; k = k + 1u) {
256
+ gathered[lane][k] = gathered[lane][k] + gathered[lane + half][k];
257
+ }
258
+ }
259
+ workgroupBarrier();
260
+ }
261
+ if (lane == 0u) {
262
+ let pair = pairOf[start + slot] * GRADS;
263
+ for (var k = 0u; k < GRADS; k = k + 1u) { partials[pair + k] = gathered[0][k]; }
264
+ }
265
+ workgroupBarrier();
266
+ }
267
+ }
268
+ `;
269
+ /**
270
+ * Each splat's own run of partial gradients, summed in index order.
271
+ *
272
+ * Bindings: `params`, `pairOffsets`, `tilesPerSplat`, `partials`, `gradients`.
273
+ */
274
+ export const SPLAT_REDUCE_WGSL = /* wgsl */ `
275
+ @group(0) @binding(0) var<uniform> params: Params;
276
+ @group(0) @binding(1) var<storage, read> pairOffsets: array<u32>;
277
+ @group(0) @binding(2) var<storage, read> tilesPerSplat: array<u32>;
278
+ @group(0) @binding(3) var<storage, read> partials: array<f32>;
279
+ @group(0) @binding(4) var<storage, read_write> gradients: array<f32>;
280
+ ${SHARED}
281
+
282
+ @compute @workgroup_size(64, 1, 1)
283
+ fn main(@builtin(global_invocation_id) id: vec3u) {
284
+ let splat = id.x;
285
+ if (splat >= params.splats) { return; }
286
+ let start = pairOffsets[splat];
287
+ let held = tilesPerSplat[splat];
288
+ for (var k = 0u; k < GRADS; k = k + 1u) {
289
+ var total = 0.0;
290
+ for (var slot = 0u; slot < held; slot = slot + 1u) {
291
+ total = total + partials[(start + slot) * GRADS + k];
292
+ }
293
+ gradients[splat * GRADS + k] = total;
294
+ }
295
+ }
296
+ `;
297
+ /**
298
+ * One Adam step over one family of parameters.
299
+ *
300
+ * The bias corrections arrive already computed, because they are a running product on the host —
301
+ * `β₁ⁿ` raised on the device would be a second definition of the same decay and would drift from
302
+ * the reference's in the last bits for no gain.
303
+ *
304
+ * Bindings: `settings` (count, rate, correction1, correction2), `values`, `gradient`, `moment`,
305
+ * `second`.
306
+ */
307
+ export const SPLAT_ADAM_WGSL = /* wgsl */ `
308
+ struct Settings {
309
+ count: u32,
310
+ rate: f32,
311
+ correction1: f32,
312
+ correction2: f32,
313
+ };
314
+
315
+ const BETA1: f32 = 0.9;
316
+ const BETA2: f32 = 0.999;
317
+ const EPSILON: f32 = 1e-8;
318
+
319
+ @group(0) @binding(0) var<uniform> settings: Settings;
320
+ @group(0) @binding(1) var<storage, read_write> values: array<f32>;
321
+ @group(0) @binding(2) var<storage, read> gradient: array<f32>;
322
+ @group(0) @binding(3) var<storage, read_write> moment: array<f32>;
323
+ @group(0) @binding(4) var<storage, read_write> second: array<f32>;
324
+
325
+ @compute @workgroup_size(64, 1, 1)
326
+ fn main(@builtin(global_invocation_id) id: vec3u) {
327
+ let at = id.x;
328
+ if (at >= settings.count) { return; }
329
+ let g = gradient[at];
330
+ let m = BETA1 * moment[at] + (1.0 - BETA1) * g;
331
+ let v = BETA2 * second[at] + (1.0 - BETA2) * g * g;
332
+ moment[at] = m;
333
+ second[at] = v;
334
+ values[at] = values[at]
335
+ - (settings.rate * (m / settings.correction1)) / (sqrt(v / settings.correction2) + EPSILON);
336
+ }
337
+ `;