@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,107 @@
1
+ import { LINEAR_FOG } from '../../fog.js';
2
+ /**
3
+ * The medium between the camera and a GPU-driven surface, as WGSL.
4
+ *
5
+ * **`render/fog.ts` is the reference and this is checked against it**, over generated cases on a
6
+ * device by `scripts/gpu-parity.mjs` — the pattern `gpudriven/lit.ts` and `gpudriven/alphaTest.ts`
7
+ * are both written under, and for the reason this file exists at all: the forward path's fog is
8
+ * GLSL, so the two pipelines cannot share text and can only be held to one another by measurement.
9
+ *
10
+ * **Two shaders include this, and neither restates it.** The shading pass fogs an opaque surface
11
+ * and the blended raster fogs a pane, and a pane at the render edge that did not take the haze
12
+ * would stand out of it. The previous wave found the weighted-transparency weight written twice
13
+ * after its gate had been watching the copy, so this one starts shared.
14
+ *
15
+ * The including module declares the `fog` block — `frameBlock.wgsl.ts` does, so both readers get
16
+ * one layout — and every guard and constant below is `render/fog.ts`'s, which is `shaders/fog.ts`'s.
17
+ */
18
+ export const FOG_WGSL = /* wgsl */ `
19
+ /** What distance converges toward: the day's haze, or the water column. */
20
+ fn mediumColour() -> vec3<f32> {
21
+ return mix(frame.fogColour.xyz, frame.fogUnderwater.xyz, frame.fogUnderwater.w);
22
+ }
23
+
24
+ /**
25
+ * How much of a surface at "pointY", "dist" metres away, is lost to the medium between.
26
+ * 0 is perfectly clear, 1 is the medium's own colour.
27
+ */
28
+ fn mediumFog(dist: f32, pointY: f32) -> f32 {
29
+ let underwaterFactor = frame.fogUnderwater.w;
30
+ if (frame.fogRange.z == ${LINEAR_FOG}.0) {
31
+ /*
32
+ * A consumer asking for a hard cut writes far == near, and the guard is why that is a cut and
33
+ * not a NaN at the one distance where the quotient is zero over zero.
34
+ *
35
+ * **This copy of it is not observable on the machine this was written on**, and the perturbation
36
+ * says so: removing it changes nothing the parity check can see, because WGSL does not require
37
+ * NaN to be supported and this device answers 0 for 0.0/0.0 rather than NaN. The guard stays
38
+ * because render/fog.ts needs it — its own test fails without it — and because a device that
39
+ * does propagate NaN would paint the fog colour along the cut. The alpha test found the same
40
+ * shape from the other direction on 2026-09-18: a device may assume NaN never arrives.
41
+ */
42
+ let span = max(frame.fogRange.y - frame.fogRange.x, 1e-4);
43
+ let ramp = clamp((dist - frame.fogRange.x) / span, 0.0, 1.0);
44
+ if (underwaterFactor <= 0.0) { return ramp; }
45
+ let wetLinear = frame.fogRange.w * dist;
46
+ return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), underwaterFactor);
47
+ }
48
+
49
+ let t = (pointY - frame.fogColour.w) * frame.fogHeight.x;
50
+ /* (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than branched: at t = 0
51
+ the quotient is 0/0, and 1e-4 of a scale height is 8 mm. */
52
+ var denom = t;
53
+ if (abs(t) < 1e-4) { denom = 1e-4; }
54
+ let air = 1.0 - exp(-frame.fogHeight.y * dist * (1.0 - exp(-denom)) / denom);
55
+ if (underwaterFactor <= 0.0) { return air; }
56
+ /* Water is its own medium and keeps its own curve, so crossing the surface changes nothing
57
+ about how the water column reads. */
58
+ let wet = frame.fogRange.w * dist;
59
+ return mix(air, 1.0 - exp2(-wet * wet * 1.442695), underwaterFactor);
60
+ }
61
+ `;
62
+ /** Floats a case of the fog check: distance, height, and the ten options. */
63
+ export const FOG_CASE_FLOATS = 16;
64
+ /**
65
+ * An entry point that does nothing but fog, so `gpu-parity.mjs` can compare the expression above
66
+ * with `render/fog.ts` directly rather than through a shading pass that writes to a texture.
67
+ *
68
+ * **It declares its own `frame` and fills it from the case**, rather than restating the arithmetic:
69
+ * the whole point is that the function under test is the one the frame draws with, and a second
70
+ * copy here is a gate watching a copy of what it guards.
71
+ */
72
+ export const FOG_PARITY_WGSL = /* wgsl */ `
73
+ struct Frame {
74
+ /** rgb is the haze's colour, w is the eye's height. */
75
+ fogColour: vec4<f32>,
76
+ /** rgb is the water's colour, w is how far under it the camera is. */
77
+ fogUnderwater: vec4<f32>,
78
+ /** Reciprocal scale height, extinction per metre at the eye, and two spare. */
79
+ fogHeight: vec4<f32>,
80
+ /** Linear near and far, the mode, and the water's own density. */
81
+ fogRange: vec4<f32>,
82
+ }
83
+
84
+ @group(0) @binding(0) var<storage, read> cases: array<f32>;
85
+ @group(0) @binding(1) var<storage, read_write> results: array<f32>;
86
+
87
+ var<private> frame: Frame;
88
+ ${FOG_WGSL}
89
+ @compute @workgroup_size(64)
90
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
91
+ let at = id.x * ${FOG_CASE_FLOATS}u;
92
+ if (at + ${FOG_CASE_FLOATS - 1}u >= arrayLength(&cases)) { return; }
93
+ let dist = cases[at];
94
+ let pointY = cases[at + 1u];
95
+ frame.fogColour = vec4<f32>(cases[at + 2u], cases[at + 3u], cases[at + 4u], cases[at + 5u]);
96
+ frame.fogUnderwater = vec4<f32>(cases[at + 6u], cases[at + 7u], cases[at + 8u], cases[at + 9u]);
97
+ frame.fogHeight = vec4<f32>(cases[at + 10u], cases[at + 11u], 0.0, 0.0);
98
+ frame.fogRange = vec4<f32>(cases[at + 12u], cases[at + 13u], cases[at + 14u], cases[at + 15u]);
99
+
100
+ let out = id.x * 4u;
101
+ results[out] = mediumFog(dist, pointY);
102
+ let colour = mediumColour();
103
+ results[out + 1u] = colour.x;
104
+ results[out + 2u] = colour.y;
105
+ results[out + 3u] = colour.z;
106
+ }
107
+ `;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * What the frame tells every stage that lights something, declared once.
3
+ *
4
+ * **Two stages light a surface now**: the shading pass, which resolves the visibility buffer, and
5
+ * the blended raster, which shades a transparent fragment where it stands. They read the same
6
+ * block because they are given the same frame, and a second declaration of it would be twelve
7
+ * `vec4`s whose *offsets* have to agree — a field inserted in one and not the other does not fail
8
+ * to compile, it reads the neighbouring one, so a light direction becomes a sky colour.
9
+ *
10
+ * `gpuDrivenPass.ts` writes it through the `FRAME_*` offsets beside `FRAME_FLOATS`, which are the
11
+ * other half of this contract.
12
+ */
13
+ export declare const FRAME_BLOCK_WGSL = "\nstruct Frame {\n viewProj: mat4x4<f32>,\n /*\n * The light's, corrected for the lookup rather than for the raster: x and y still in -1..1 and\n * z already in 0..1. gpudriven/shadowCamera.ts carries why those are two different matrices.\n */\n lightViewProj: mat4x4<f32>,\n eye: vec4<f32>,\n lightDir: vec4<f32>,\n lightColour: vec4<f32>,\n sky: vec4<f32>,\n ground: vec4<f32>,\n /** width, height, the simulation time, and the latent layer's edge in texels. */\n size: vec4<f32>,\n /** The shadow: strength, map size, depth span, the distance it fades over. */\n shadow: vec4<f32>,\n /** Its remaining limits: the steepest sun that still casts, the tap count, and two spare. */\n shadowLimits: vec4<f32>,\n /** The probe: texels across level 0, the coarsest GGX level, the cosine level, and 1 if bound. */\n environment: vec4<f32>,\n /**\n * How much of the hemispheric ambient the probe's irradiance replaces, the frame's emissive gain\n * times its night factor, and two spare.\n */\n environmentMix: vec4<f32>,\n /**\n * The medium, in four blocks, and render/fog.ts is what reads them.\n *\n * **A frame that asks for nothing draws nothing**: a density of zero and the medium mode make\n * mediumFog exactly zero at every distance, which is arithmetically the frame the pipeline drew\n * before it had a haze at all.\n */\n /** rgb is the haze's colour, w is the eye's height. */\n fogColour: vec4<f32>,\n /** rgb is the water's colour, w is how far under it the camera is. */\n fogUnderwater: vec4<f32>,\n /** Reciprocal scale height, extinction per metre at the eye, and two spare. */\n fogHeight: vec4<f32>,\n /** Linear near and far, the mode, and the water's own density. */\n fogRange: vec4<f32>,\n /**\n * The visibility buffer's row in texels \u2014 the width rounded up for the copy's row alignment \u2014\n * and three spare. A pixel is found by this and placed by size.x, which is the width the raster\n * drew at; the two differ on every frame whose width is not a multiple of sixty-four. Not\n * \"target\", which WGSL reserves.\n */\n pitch: vec4<f32>,\n}\n";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * What the frame tells every stage that lights something, declared once.
3
+ *
4
+ * **Two stages light a surface now**: the shading pass, which resolves the visibility buffer, and
5
+ * the blended raster, which shades a transparent fragment where it stands. They read the same
6
+ * block because they are given the same frame, and a second declaration of it would be twelve
7
+ * `vec4`s whose *offsets* have to agree — a field inserted in one and not the other does not fail
8
+ * to compile, it reads the neighbouring one, so a light direction becomes a sky colour.
9
+ *
10
+ * `gpuDrivenPass.ts` writes it through the `FRAME_*` offsets beside `FRAME_FLOATS`, which are the
11
+ * other half of this contract.
12
+ */
13
+ export const FRAME_BLOCK_WGSL = /* wgsl */ `
14
+ struct Frame {
15
+ viewProj: mat4x4<f32>,
16
+ /*
17
+ * The light's, corrected for the lookup rather than for the raster: x and y still in -1..1 and
18
+ * z already in 0..1. gpudriven/shadowCamera.ts carries why those are two different matrices.
19
+ */
20
+ lightViewProj: mat4x4<f32>,
21
+ eye: vec4<f32>,
22
+ lightDir: vec4<f32>,
23
+ lightColour: vec4<f32>,
24
+ sky: vec4<f32>,
25
+ ground: vec4<f32>,
26
+ /** width, height, the simulation time, and the latent layer's edge in texels. */
27
+ size: vec4<f32>,
28
+ /** The shadow: strength, map size, depth span, the distance it fades over. */
29
+ shadow: vec4<f32>,
30
+ /** Its remaining limits: the steepest sun that still casts, the tap count, and two spare. */
31
+ shadowLimits: vec4<f32>,
32
+ /** The probe: texels across level 0, the coarsest GGX level, the cosine level, and 1 if bound. */
33
+ environment: vec4<f32>,
34
+ /**
35
+ * How much of the hemispheric ambient the probe's irradiance replaces, the frame's emissive gain
36
+ * times its night factor, and two spare.
37
+ */
38
+ environmentMix: vec4<f32>,
39
+ /**
40
+ * The medium, in four blocks, and render/fog.ts is what reads them.
41
+ *
42
+ * **A frame that asks for nothing draws nothing**: a density of zero and the medium mode make
43
+ * mediumFog exactly zero at every distance, which is arithmetically the frame the pipeline drew
44
+ * before it had a haze at all.
45
+ */
46
+ /** rgb is the haze's colour, w is the eye's height. */
47
+ fogColour: vec4<f32>,
48
+ /** rgb is the water's colour, w is how far under it the camera is. */
49
+ fogUnderwater: vec4<f32>,
50
+ /** Reciprocal scale height, extinction per metre at the eye, and two spare. */
51
+ fogHeight: vec4<f32>,
52
+ /** Linear near and far, the mode, and the water's own density. */
53
+ fogRange: vec4<f32>,
54
+ /**
55
+ * The visibility buffer's row in texels — the width rounded up for the copy's row alignment —
56
+ * and three spare. A pixel is found by this and placed by size.x, which is the width the raster
57
+ * drew at; the two differ on every frame whose width is not a multiple of sixty-four. Not
58
+ * "target", which WGSL reserves.
59
+ */
60
+ pitch: vec4<f32>,
61
+ }
62
+ `;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Binning the frame's pixels by material, in three dispatches.
3
+ *
4
+ * **Count, then sum, then scatter**, which is the only arrangement that gives each bin a
5
+ * contiguous slice without sorting: a pixel cannot know where it belongs until every pixel before
6
+ * it has been counted, so the count has to finish before the offsets exist and the offsets have to
7
+ * exist before anything is written.
8
+ *
9
+ * **The scatter's order inside a bin is whatever the atomics produced**, and nothing downstream
10
+ * may depend on it. Shading reads a bin as a set. `scripts/gpu-parity.mjs` compares the bins as
11
+ * sets for the same reason, and compares the *counts* exactly — which are order-free and are where
12
+ * a binning defect actually shows.
13
+ *
14
+ * **The sentinel test in the count and scatter passes survives perturbation**, and it is kept for
15
+ * the reason `materialBin.ts` states at length: `VIS_EMPTY`'s cluster field reads as 33,554,431,
16
+ * so the range test below it refuses the same pixels. The range test is the one a real frame
17
+ * exercises — a visibility buffer left over from a frame with more geometry in it — and it is
18
+ * caught.
19
+ *
20
+ * Hand-written for the reason `cull.wgsl.ts` gives: WebGL2 has no compute stage, so there is no
21
+ * GLSL original and nothing this can break by being wrong.
22
+ */
23
+ /**
24
+ * Pass one: how many pixels each material owns.
25
+ *
26
+ * The counts buffer must be **cleared before this runs**. A count buffer reused across frames
27
+ * without clearing grows without bound, the offsets then run past the end of the pixel list, and
28
+ * the shading reads a neighbouring bin — which looks like one material bleeding into another.
29
+ */
30
+ export declare const MATERIAL_COUNT_WGSL = "\nconst VIS_TRIANGLE_BITS: u32 = 7u;\nconst VIS_EMPTY: u32 = 0xffffffffu;\n\n/** pixelCount, materialCount, groupSize. */\n@group(0) @binding(0) var<storage, read> settings: array<u32, 4>;\n@group(0) @binding(1) var<storage, read> visibility: array<u32>;\n@group(0) @binding(2) var<storage, read> materialOf: array<u32>;\n\n@group(0) @binding(3) var<storage, read_write> counts: array<atomic<u32>>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= settings[0]) { return; }\n let packed = visibility[id.x];\n if (packed == VIS_EMPTY) { return; }\n let cluster = packed >> VIS_TRIANGLE_BITS;\n if (cluster >= arrayLength(&materialOf)) { return; }\n let material = materialOf[cluster];\n if (material >= settings[1]) { return; }\n atomicAdd(&counts[material], 1u);\n}\n";
31
+ /**
32
+ * Pass two: the offsets, as an exclusive prefix sum, and the dispatch block for each material.
33
+ *
34
+ * **One invocation, deliberately.** A material count is tens, not millions, and a parallel scan
35
+ * over tens of elements costs more in barriers than the serial loop costs in cycles. The comment
36
+ * is here because "this is not parallel" reads as an oversight in a compute shader and is a
37
+ * measurement: the scan would be three barriers against a loop of at most a few hundred adds.
38
+ *
39
+ * **Every material gets a block, including an empty one**, whose group count is zero. Dropping it
40
+ * shifts every later material's arguments and shades the scene with the wrong code. The zero is
41
+ * the round-up's own answer rather than a branch: a `select` stood here and a perturbation
42
+ * survived it, because `(0 + n - 1) / n` is already zero.
43
+ */
44
+ export declare const MATERIAL_OFFSETS_WGSL = "\nconst VIS_TRIANGLE_BITS: u32 = 7u;\nconst VIS_EMPTY: u32 = 0xffffffffu;\n\n/** pixelCount, materialCount, groupSize. */\n@group(0) @binding(0) var<storage, read> settings: array<u32, 4>;\n@group(0) @binding(1) var<storage, read> visibility: array<u32>;\n@group(0) @binding(2) var<storage, read> materialOf: array<u32>;\n\n@group(0) @binding(3) var<storage, read> counts: array<u32>;\n@group(0) @binding(4) var<storage, read_write> offsets: array<u32>;\n@group(0) @binding(5) var<storage, read_write> dispatches: array<u32>;\n\n@compute @workgroup_size(1)\nfn main() {\n let materials = settings[1];\n let groupSize = max(1u, settings[2]);\n var running = 0u;\n for (var material = 0u; material < materials; material = material + 1u) {\n offsets[material] = running;\n let held = counts[material];\n running = running + held;\n /* Zero falls out of the division: an empty bin is zero groups with no special case. */\n let groups = (held + groupSize - 1u) / groupSize;\n dispatches[material * 3u] = groups;\n dispatches[material * 3u + 1u] = 1u;\n dispatches[material * 3u + 2u] = 1u;\n }\n}\n";
45
+ /**
46
+ * Pass three: every covered pixel's index into its material's slice.
47
+ *
48
+ * `cursors` is a copy of the offsets rather than the offsets themselves, because the offsets have
49
+ * to survive this pass — they are what tells a shading group where its material's list begins.
50
+ */
51
+ export declare const MATERIAL_SCATTER_WGSL = "\nconst VIS_TRIANGLE_BITS: u32 = 7u;\nconst VIS_EMPTY: u32 = 0xffffffffu;\n\n/** pixelCount, materialCount, groupSize. */\n@group(0) @binding(0) var<storage, read> settings: array<u32, 4>;\n@group(0) @binding(1) var<storage, read> visibility: array<u32>;\n@group(0) @binding(2) var<storage, read> materialOf: array<u32>;\n\n@group(0) @binding(3) var<storage, read_write> cursors: array<atomic<u32>>;\n@group(0) @binding(4) var<storage, read_write> pixels: array<u32>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= settings[0]) { return; }\n let packed = visibility[id.x];\n if (packed == VIS_EMPTY) { return; }\n let cluster = packed >> VIS_TRIANGLE_BITS;\n if (cluster >= arrayLength(&materialOf)) { return; }\n let material = materialOf[cluster];\n if (material >= settings[1]) { return; }\n let at = atomicAdd(&cursors[material], 1u);\n pixels[at] = id.x;\n}\n";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Binning the frame's pixels by material, in three dispatches.
3
+ *
4
+ * **Count, then sum, then scatter**, which is the only arrangement that gives each bin a
5
+ * contiguous slice without sorting: a pixel cannot know where it belongs until every pixel before
6
+ * it has been counted, so the count has to finish before the offsets exist and the offsets have to
7
+ * exist before anything is written.
8
+ *
9
+ * **The scatter's order inside a bin is whatever the atomics produced**, and nothing downstream
10
+ * may depend on it. Shading reads a bin as a set. `scripts/gpu-parity.mjs` compares the bins as
11
+ * sets for the same reason, and compares the *counts* exactly — which are order-free and are where
12
+ * a binning defect actually shows.
13
+ *
14
+ * **The sentinel test in the count and scatter passes survives perturbation**, and it is kept for
15
+ * the reason `materialBin.ts` states at length: `VIS_EMPTY`'s cluster field reads as 33,554,431,
16
+ * so the range test below it refuses the same pixels. The range test is the one a real frame
17
+ * exercises — a visibility buffer left over from a frame with more geometry in it — and it is
18
+ * caught.
19
+ *
20
+ * Hand-written for the reason `cull.wgsl.ts` gives: WebGL2 has no compute stage, so there is no
21
+ * GLSL original and nothing this can break by being wrong.
22
+ */
23
+ /** Shared declarations, so the three entry points cannot disagree about the layout. */
24
+ const BIN_PREAMBLE = `
25
+ const VIS_TRIANGLE_BITS: u32 = 7u;
26
+ const VIS_EMPTY: u32 = 0xffffffffu;
27
+
28
+ /** pixelCount, materialCount, groupSize. */
29
+ @group(0) @binding(0) var<storage, read> settings: array<u32, 4>;
30
+ @group(0) @binding(1) var<storage, read> visibility: array<u32>;
31
+ @group(0) @binding(2) var<storage, read> materialOf: array<u32>;
32
+ `;
33
+ /**
34
+ * Pass one: how many pixels each material owns.
35
+ *
36
+ * The counts buffer must be **cleared before this runs**. A count buffer reused across frames
37
+ * without clearing grows without bound, the offsets then run past the end of the pixel list, and
38
+ * the shading reads a neighbouring bin — which looks like one material bleeding into another.
39
+ */
40
+ export const MATERIAL_COUNT_WGSL = `${BIN_PREAMBLE}
41
+ @group(0) @binding(3) var<storage, read_write> counts: array<atomic<u32>>;
42
+
43
+ @compute @workgroup_size(64)
44
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
45
+ if (id.x >= settings[0]) { return; }
46
+ let packed = visibility[id.x];
47
+ if (packed == VIS_EMPTY) { return; }
48
+ let cluster = packed >> VIS_TRIANGLE_BITS;
49
+ if (cluster >= arrayLength(&materialOf)) { return; }
50
+ let material = materialOf[cluster];
51
+ if (material >= settings[1]) { return; }
52
+ atomicAdd(&counts[material], 1u);
53
+ }
54
+ `;
55
+ /**
56
+ * Pass two: the offsets, as an exclusive prefix sum, and the dispatch block for each material.
57
+ *
58
+ * **One invocation, deliberately.** A material count is tens, not millions, and a parallel scan
59
+ * over tens of elements costs more in barriers than the serial loop costs in cycles. The comment
60
+ * is here because "this is not parallel" reads as an oversight in a compute shader and is a
61
+ * measurement: the scan would be three barriers against a loop of at most a few hundred adds.
62
+ *
63
+ * **Every material gets a block, including an empty one**, whose group count is zero. Dropping it
64
+ * shifts every later material's arguments and shades the scene with the wrong code. The zero is
65
+ * the round-up's own answer rather than a branch: a `select` stood here and a perturbation
66
+ * survived it, because `(0 + n - 1) / n` is already zero.
67
+ */
68
+ export const MATERIAL_OFFSETS_WGSL = `${BIN_PREAMBLE}
69
+ @group(0) @binding(3) var<storage, read> counts: array<u32>;
70
+ @group(0) @binding(4) var<storage, read_write> offsets: array<u32>;
71
+ @group(0) @binding(5) var<storage, read_write> dispatches: array<u32>;
72
+
73
+ @compute @workgroup_size(1)
74
+ fn main() {
75
+ let materials = settings[1];
76
+ let groupSize = max(1u, settings[2]);
77
+ var running = 0u;
78
+ for (var material = 0u; material < materials; material = material + 1u) {
79
+ offsets[material] = running;
80
+ let held = counts[material];
81
+ running = running + held;
82
+ /* Zero falls out of the division: an empty bin is zero groups with no special case. */
83
+ let groups = (held + groupSize - 1u) / groupSize;
84
+ dispatches[material * 3u] = groups;
85
+ dispatches[material * 3u + 1u] = 1u;
86
+ dispatches[material * 3u + 2u] = 1u;
87
+ }
88
+ }
89
+ `;
90
+ /**
91
+ * Pass three: every covered pixel's index into its material's slice.
92
+ *
93
+ * `cursors` is a copy of the offsets rather than the offsets themselves, because the offsets have
94
+ * to survive this pass — they are what tells a shading group where its material's list begins.
95
+ */
96
+ export const MATERIAL_SCATTER_WGSL = `${BIN_PREAMBLE}
97
+ @group(0) @binding(3) var<storage, read_write> cursors: array<atomic<u32>>;
98
+ @group(0) @binding(4) var<storage, read_write> pixels: array<u32>;
99
+
100
+ @compute @workgroup_size(64)
101
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
102
+ if (id.x >= settings[0]) { return; }
103
+ let packed = visibility[id.x];
104
+ if (packed == VIS_EMPTY) { return; }
105
+ let cluster = packed >> VIS_TRIANGLE_BITS;
106
+ if (cluster >= arrayLength(&materialOf)) { return; }
107
+ let material = materialOf[cluster];
108
+ if (material >= settings[1]) { return; }
109
+ let at = atomicAdd(&cursors[material], 1u);
110
+ pixels[at] = id.x;
111
+ }
112
+ `;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The material table's layout, declared once for everything that reads it.
3
+ *
4
+ * **Two shaders read this table now and they used to be one.** The shading pass has always read a
5
+ * material; since the raster gained an alpha test it reads one too, because a pixel learns which
6
+ * triangle owns it there and a discard any later is a discard after the visibility buffer already
7
+ * held the leaf. A second copy of the struct would be twenty lanes maintained in two places, which
8
+ * is the failure `cluster-check.mjs`'s header describes at length: two declarations of one decision
9
+ * drift invisibly, and the thing that breaks is a scalar read as a program index.
10
+ *
11
+ * `gpudriven/materialTable.ts` writes the lanes these fields land on and its own header says why
12
+ * some of them are written through a `DataView`.
13
+ */
14
+ /**
15
+ * How many materials the table holds, and it is a declared ceiling rather than a discovered one.
16
+ *
17
+ * A uniform array in WGSL is fixed length, and the table is a uniform rather than a storage buffer
18
+ * because a compute stage may bind only **eight** storage buffers by default — this pass needed
19
+ * twelve when it was first written, and the device said so at pipeline creation. Sixty-four
20
+ * materials is 2 kB of the 64 kB a uniform buffer may hold; a scene wanting more wants a storage
21
+ * buffer and one of the eight slots back from somewhere.
22
+ */
23
+ export declare const MATERIAL_SLOTS = 64;
24
+ /** The struct and the sentinel, for any module that binds the table. */
25
+ export declare const MATERIAL_TABLE_WGSL = "\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, and three spare. */\n extra: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The material table's layout, declared once for everything that reads it.
3
+ *
4
+ * **Two shaders read this table now and they used to be one.** The shading pass has always read a
5
+ * material; since the raster gained an alpha test it reads one too, because a pixel learns which
6
+ * triangle owns it there and a discard any later is a discard after the visibility buffer already
7
+ * held the leaf. A second copy of the struct would be twenty lanes maintained in two places, which
8
+ * is the failure `cluster-check.mjs`'s header describes at length: two declarations of one decision
9
+ * drift invisibly, and the thing that breaks is a scalar read as a program index.
10
+ *
11
+ * `gpudriven/materialTable.ts` writes the lanes these fields land on and its own header says why
12
+ * some of them are written through a `DataView`.
13
+ */
14
+ import { DECODE_NO_PROGRAM } from './decode.wgsl.js';
15
+ /**
16
+ * How many materials the table holds, and it is a declared ceiling rather than a discovered one.
17
+ *
18
+ * A uniform array in WGSL is fixed length, and the table is a uniform rather than a storage buffer
19
+ * because a compute stage may bind only **eight** storage buffers by default — this pass needed
20
+ * twelve when it was first written, and the device said so at pipeline creation. Sixty-four
21
+ * materials is 2 kB of the 64 kB a uniform buffer may hold; a scene wanting more wants a storage
22
+ * buffer and one of the eight slots back from somewhere.
23
+ */
24
+ export const MATERIAL_SLOTS = 64;
25
+ /** The struct and the sentinel, for any module that binds the table. */
26
+ export const MATERIAL_TABLE_WGSL = /* wgsl */ `
27
+ struct Material {
28
+ /** rgb is the tint, w is the emissive gain. */
29
+ tint: vec4<f32>,
30
+ /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */
31
+ surface: vec4<f32>,
32
+ /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */
33
+ programs: vec4<u32>,
34
+ /** u scale, v scale, normal strength, occlusion strength. */
35
+ maps: vec4<f32>,
36
+ /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */
37
+ orm: vec4<f32>,
38
+ /** The blended opacity, and three spare. */
39
+ extra: vec4<f32>,
40
+ }
41
+
42
+ const NO_PROGRAM: u32 = ${DECODE_NO_PROGRAM}u;
43
+ `;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Placing a mesh's vertex in the world, for every stage of the pipeline that has to.
3
+ *
4
+ * **Three stages do it now and each had its own copy of it.** The visibility raster called it
5
+ * `transformed`, the shading pass called it `worldOf`, and the two bodies were the same nine
6
+ * multiplies; the blended raster would have been a third. What the copies would drift about is not
7
+ * the arithmetic but the *layout* — which four floats are the translation — and a stage that reads
8
+ * the translation from the wrong column draws a mesh somewhere else entirely while every test that
9
+ * builds its scene at the origin passes.
10
+ *
11
+ * **Column major, so the translation is floats 12 to 14.** `clusterWorld.ts` writes the bounds
12
+ * through the same convention and `sceneShadowBounds` reads them, which is what keeps a cluster's
13
+ * bounding sphere over the geometry it bounds.
14
+ *
15
+ * The including module declares `transforms` — sixteen floats a mesh — at whichever binding it has
16
+ * room for.
17
+ */
18
+ export declare const MESH_TRANSFORM_WGSL = "\nfn worldOf(mesh: u32, p: vec3<f32>) -> vec3<f32> {\n let m = mesh * 16u;\n return vec3<f32>(\n transforms[m] * p.x + transforms[m + 4u] * p.y + transforms[m + 8u] * p.z + transforms[m + 12u],\n transforms[m + 1u] * p.x + transforms[m + 5u] * p.y + transforms[m + 9u] * p.z + transforms[m + 13u],\n transforms[m + 2u] * p.x + transforms[m + 6u] * p.y + transforms[m + 10u] * p.z + transforms[m + 14u],\n );\n}\n\n/* Upper three-by-three only: a direction has no translation. See the header on non-uniform scale. */\nfn rotatedOf(mesh: u32, n: vec3<f32>) -> vec3<f32> {\n let m = mesh * 16u;\n return vec3<f32>(\n transforms[m] * n.x + transforms[m + 4u] * n.y + transforms[m + 8u] * n.z,\n transforms[m + 1u] * n.x + transforms[m + 5u] * n.y + transforms[m + 9u] * n.z,\n transforms[m + 2u] * n.x + transforms[m + 6u] * n.y + transforms[m + 10u] * n.z,\n );\n}\n";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Placing a mesh's vertex in the world, for every stage of the pipeline that has to.
3
+ *
4
+ * **Three stages do it now and each had its own copy of it.** The visibility raster called it
5
+ * `transformed`, the shading pass called it `worldOf`, and the two bodies were the same nine
6
+ * multiplies; the blended raster would have been a third. What the copies would drift about is not
7
+ * the arithmetic but the *layout* — which four floats are the translation — and a stage that reads
8
+ * the translation from the wrong column draws a mesh somewhere else entirely while every test that
9
+ * builds its scene at the origin passes.
10
+ *
11
+ * **Column major, so the translation is floats 12 to 14.** `clusterWorld.ts` writes the bounds
12
+ * through the same convention and `sceneShadowBounds` reads them, which is what keeps a cluster's
13
+ * bounding sphere over the geometry it bounds.
14
+ *
15
+ * The including module declares `transforms` — sixteen floats a mesh — at whichever binding it has
16
+ * room for.
17
+ */
18
+ export const MESH_TRANSFORM_WGSL = /* wgsl */ `
19
+ fn worldOf(mesh: u32, p: vec3<f32>) -> vec3<f32> {
20
+ let m = mesh * 16u;
21
+ return vec3<f32>(
22
+ transforms[m] * p.x + transforms[m + 4u] * p.y + transforms[m + 8u] * p.z + transforms[m + 12u],
23
+ transforms[m + 1u] * p.x + transforms[m + 5u] * p.y + transforms[m + 9u] * p.z + transforms[m + 13u],
24
+ transforms[m + 2u] * p.x + transforms[m + 6u] * p.y + transforms[m + 10u] * p.z + transforms[m + 14u],
25
+ );
26
+ }
27
+
28
+ /* Upper three-by-three only: a direction has no translation. See the header on non-uniform scale. */
29
+ fn rotatedOf(mesh: u32, n: vec3<f32>) -> vec3<f32> {
30
+ let m = mesh * 16u;
31
+ return vec3<f32>(
32
+ transforms[m] * n.x + transforms[m + 4u] * n.y + transforms[m + 8u] * n.z,
33
+ transforms[m + 1u] * n.x + transforms[m + 5u] * n.y + transforms[m + 9u] * n.z,
34
+ transforms[m + 2u] * n.x + transforms[m + 6u] * n.y + transforms[m + 10u] * n.z,
35
+ );
36
+ }
37
+ `;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Putting the GPU-driven pipeline's colour into the frame it was invited to draw in.
3
+ *
4
+ * **A contributed pass cannot write into the frame's attachment directly and should not want
5
+ * to.** `pass.ts` refuses a declared write-set for a reason it states at length; what a pass gets
6
+ * instead is a target of its own and a draw call at the point the caller put it. So the pipeline
7
+ * fills a storage texture in `prepare` and this puts it on the screen in `draw` — one triangle, no
8
+ * sampler, and `textureLoad` at the fragment's own pixel.
9
+ *
10
+ * **`textureLoad` rather than a sampler, because there is nothing to filter.** The target is the
11
+ * frame's own size and the mapping is one texel to one pixel; a sampler would add a filtering mode
12
+ * to choose and a half-pixel offset to get wrong, in exchange for nothing. It also means the
13
+ * texture needs no `filterable` sample type, which `rgba16float` has but which a future `r32float`
14
+ * would not.
15
+ *
16
+ * **It writes linear light.** The output transform is the composite's, for the reason
17
+ * `PassContext` gives: with a composite the resolve grades and a pass that also graded would apply
18
+ * the curve twice. A frame without one is refused in words rather than papered over — see the
19
+ * pass's `draw`.
20
+ */
21
+ export declare const GPU_DRIVEN_BLIT_WGSL = "\nstruct Varying {\n @builtin(position) position: vec4<f32>,\n}\n\n@group(0) @binding(0) var source: texture_2d<f32>;\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertex: u32) -> Varying {\n /* One triangle covering the frame rather than two: no shared edge, no diagonal seam, and the\n third of the area outside the viewport costs nothing. */\n var corners = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var out: Varying;\n out.position = vec4<f32>(corners[vertex], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fragmentMain(in: Varying) -> @location(0) vec4<f32> {\n /* The fragment's own pixel. Clamped rather than trusted: a frame larger than the pass's target\n is a caller that did not resize, and reading out of bounds is a zero this would draw as a\n black band nobody could place. */\n let size = textureDimensions(source);\n let at = min(vec2<u32>(in.position.xy), size - vec2<u32>(1u, 1u));\n let texel = textureLoad(source, at, 0);\n /*\n * **Nothing was drawn here, so nothing is written \u2014 and that is a cost, not a correctness\n * guard.** The target is cleared to zero and a zero laid over anything premultiplied is that\n * thing, so the pixel would come out the same either way; this spares every pixel the pipeline\n * did not cover a blend. Removing it left \"depth-share-check.mjs\" green on 2026-09-18, which is\n * what says so. Alpha is the coverage: the shading writes one and a blended surface adds its own.\n *\n * **Returned as it is, premultiplied, and the pipeline blends it over.** Where the visibility\n * buffer covered the pixel the coverage is one and over is a plain write. Where only a pane did,\n * it is the pane's coverage, and the frame behind it \u2014 the sky \u2014 has to show through by the rest.\n * This used to discard below a half and write the colour as opaque, which painted a heavy pane\n * over black and dropped a light one altogether.\n */\n if (texel.a <= 0.0) { discard; }\n return texel;\n}\n";
22
+ /**
23
+ * The same blit, handing the frame the pass's depth as well as its colour.
24
+ *
25
+ * **Two pipelines in one frame need one depth buffer.** The blit above presents colour and nothing
26
+ * else, which is right for a consumer whose whole scene is this pipeline — and wrong for one that
27
+ * draws its own geometry beside it, because then a forward-path mesh behind GPU-driven geometry has
28
+ * nothing to be hidden by. No ordering fixes that: drawn before the blit it is painted over, drawn
29
+ * after it floats in front of a hill.
30
+ *
31
+ * **The formats already agree.** The pass's depth texture and the frame's both take `DEPTH_FORMAT`
32
+ * and `DEPTH_COMPARE` from `depthConvention.ts`, so this is the same format and the same reversed
33
+ * convention and there is no conversion to get wrong. `gpuDrivenPass.ts` asserts that rather than
34
+ * trusting it, because a backend that ever differed would produce a depth test that is silently
35
+ * inverted instead of one that fails.
36
+ *
37
+ * **A pixel the pipeline did not cover is discarded before it writes**, exactly as above, so the
38
+ * sky keeps the depth the frame already had rather than being pushed to the near plane. A pixel
39
+ * only a pane covered writes the depth the pass cleared to, which is the frame's own clear — the
40
+ * pipeline compares "or equal" so that it passes over the sky, and what it writes there is the
41
+ * value already there.
42
+ */
43
+ export declare const GPU_DRIVEN_BLIT_DEPTH_WGSL = "\nstruct Varying {\n @builtin(position) position: vec4<f32>,\n}\n\nstruct Presented {\n @location(0) colour: vec4<f32>,\n @builtin(frag_depth) depth: f32,\n}\n\n@group(0) @binding(0) var source: texture_2d<f32>;\n@group(0) @binding(1) var sourceDepth: texture_depth_2d;\n\n@vertex\nfn vertexMain(@builtin(vertex_index) vertex: u32) -> Varying {\n var corners = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var out: Varying;\n out.position = vec4<f32>(corners[vertex], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fragmentMain(in: Varying) -> Presented {\n let size = textureDimensions(source);\n let at = min(vec2<u32>(in.position.xy), size - vec2<u32>(1u, 1u));\n let texel = textureLoad(source, at, 0);\n /* A cost here too: the depth the pass cleared to passes only where the frame holds that same\n clear, so the write would change nothing. The colour blit above says how that was measured. */\n if (texel.a <= 0.0) { discard; }\n var out: Presented;\n out.colour = texel;\n /* The depth this pixel was rastered at, so the frame tests against the surface it can see. */\n out.depth = textureLoad(sourceDepth, at, 0);\n return out;\n}\n";